PageRenderTime 41ms CodeModel.GetById 16ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/protocols/jain-mgcp/stack/src/main/java/org/mobicents/protocols/mgcp/stack/TransactionHandler.java

http://mobicents.googlecode.com/
Java | 669 lines | 326 code | 120 blank | 223 comment | 33 complexity | da18c0db490a3850552d0af331332c48 MD5 | raw file
  1/*
  2 * JBoss, Home of Professional Open Source
  3 * Copyright 2011, Red Hat, Inc. and individual contributors
  4 * by the @authors tag. See the copyright.txt in the distribution for a
  5 * full listing of individual contributors.
  6 *
  7 * This is free software; you can redistribute it and/or modify it
  8 * under the terms of the GNU Lesser General Public License as
  9 * published by the Free Software Foundation; either version 2.1 of
 10 * the License, or (at your option) any later version.
 11 *
 12 * This software is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 15 * Lesser General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU Lesser General Public
 18 * License along with this software; if not, write to the Free
 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 21 */
 22
 23/*
 24 * File Name     : TransactionHandle.java
 25 *
 26 * The JAIN MGCP API implementaion.
 27 *
 28 * The source code contained in this file is in in the public domain.
 29 * It can be used in any project or product without prior permission,
 30 * license or royalty payments. There is  NO WARRANTY OF ANY KIND,
 31 * EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION,
 32 * THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 33 * AND DATA ACCURACY.  We do not warrant or make any representations
 34 * regarding the use of the software or the  results thereof, including
 35 * but not limited to the correctness, accuracy, reliability or
 36 * usefulness of the software.
 37 */
 38package org.mobicents.protocols.mgcp.stack;
 39
 40import jain.protocol.ip.mgcp.JainMgcpCommandEvent;
 41import jain.protocol.ip.mgcp.JainMgcpResponseEvent;
 42import jain.protocol.ip.mgcp.message.Constants;
 43import jain.protocol.ip.mgcp.message.Notify;
 44import jain.protocol.ip.mgcp.message.parms.EndpointIdentifier;
 45import jain.protocol.ip.mgcp.message.parms.NotifiedEntity;
 46import jain.protocol.ip.mgcp.message.parms.ReturnCode;
 47
 48import java.net.InetAddress;
 49import java.net.InetSocketAddress;
 50import java.net.UnknownHostException;
 51import java.text.ParseException;
 52import java.util.Timer;
 53import java.util.TimerTask;
 54
 55import org.apache.log4j.Logger;
 56import org.mobicents.protocols.mgcp.parser.UtilsFactory;
 57
 58/**
 59 * Implements the base gateway control interface.
 60 * 
 61 * The MGCP implements the media gateway control interface as a set of transactions. The transactions are composed of a
 62 * command and a mandatory response. There are eight types of command:
 63 * 
 64 * <li>CreateConnection ModifyConnection DeleteConnection NotificationRequest Notify AuditEndpoint AuditConnection
 65 * RestartInProgress</li>
 66 * 
 67 * The first four commands are sent by the Call Agent to a gateway. The Notify command is sent by the gateway to the
 68 * Call Agent. The gateway may also send a DeleteConnection. The Call Agent may send either of the Audit commands to the
 69 * gateway. The Gateway may send a RestartInProgress command to the Call Agent.
 70 * 
 71 * All commands are composed of a Command header, optionally followed by a session description.
 72 * 
 73 * All responses are composed of a Response header, optionally followed by a session description.
 74 * 
 75 * Headers and session descriptions are encoded as a set of text lines, separated by a line feed character. The headers
 76 * are separated from the session description by an empty line.
 77 * 
 78 * MGCP uses a transaction identifier to correlate commands and responses. The transaction identifier is encoded as a
 79 * component of the command header and repeated as a component of the response header.
 80 * 
 81 * Transaction identifiers have values between 1 and 999999999. An MGCP entity shall not reuse a transaction identifier
 82 * sooner than 3 minutes after completion of the previous command in which the identifier was used.
 83 * 
 84 * @author Oleg Kulikov
 85 * @author Pavel Mitrenko
 86 * @author Amit Bhayani
 87 */
 88// public abstract class TransactionHandler implements Runnable,
 89// TransactionHandlerManagement {
 90public abstract class TransactionHandler implements Runnable {
 91	/** Logger instance */
 92	private static final Logger logger = Logger.getLogger(TransactionHandler.class);
 93
 94	private static int GENERATOR = (int) (System.currentTimeMillis() & 999999999);
 95
 96	public static final String NEW_LINE = "\n";
 97	public static final String SINGLE_CHAR_SPACE = " ";
 98	public static final String MGCP_VERSION = " MGCP 1.0"; // let the single
 99	// char space prefix
100	// the version
101
102	public final static int LONGTRAN_TIMER_TIMEOUT = 5000; // 5secs
103
104	public static final int THIST_TIMER_TIMEOUT = 30000; // 30 sec
105	/** Is this a transaction on a command sent or received? */
106	protected boolean sent;
107	/** Transaction handle sent from application to the MGCP provider. */
108	protected int remoteTID;
109	/** Transaction handle sent from MGCP provider to MGCP listener */
110	private int localTID;
111	protected JainMgcpStackImpl stack;
112	/** Holds the address from wich request was originaly received by provider */
113	private InetAddress remoteAddress;
114
115	/**
116	 * Holds the port number from wich request was originaly received by provider
117	 */
118	private int remotePort;
119	/** Used to hold parsed command event */
120	protected JainMgcpCommandEvent commandEvent;
121
122	/** Used to hold parsed response event * */
123	protected JainMgcpResponseEvent responseEvent;
124
125	/** Expiration timer */
126	protected static Timer transactionHandlerTimer = new Timer("TransactionHandlerTimer");
127	private LongtranTimerTask longtranTimerTask;
128
129	/** Flag to check if this is Command or Response event * */
130	private boolean isCommand = false;
131
132	private ReTransmissionTimerTask reTransmissionTimer;
133
134	private THISTTimerTask tHISTTimerTask;
135
136	private int A = 0;
137	private int D = 2;
138	private int N = 2;
139
140	// private DatagramPacket sendComandDatagram = null;
141
142	private int countOfCommandRetransmitted = 0;
143
144	protected UtilsFactory utilsFactory = null;
145
146	// protected EndpointHandler endpointHandler = null;
147
148	protected boolean retransmision;
149
150	protected Object source = null;
151
152	private String msgTemp = null;
153
154	protected EndpointIdentifier endpoint = null;
155
156	private byte[] data;
157	private InetSocketAddress inetSocketAddress = null;
158
159	/**
160	 * Creates a new instance of TransactionHandle
161	 * 
162	 * Used by provider to prepare origination transaction for sending command message from an application to the stack.
163	 * 
164	 * @param stack
165	 *            the reference to the MGCP stack.
166	 */
167	public TransactionHandler(JainMgcpStackImpl stack) {
168		this.stack = stack;
169		this.localTID = GENERATOR++;
170		// utils = new Utils();
171		utilsFactory = stack.getUtilsFactory();
172		stack.getLocalTransactions().put(Integer.valueOf(localTID), this);
173		// if (logger.isDebugEnabled()) {
174		// logger.debug("New mgcp transaction with id localID=" + localTID);
175		// }
176	}
177
178	/**
179	 * Creates a new instance of TransactionHandle.
180	 * 
181	 * Used by stack to prepare transaction for transmitting message from provider to the application.
182	 * 
183	 * @param stack
184	 *            the reference to the MGCP stack.
185	 * @remoteAddress the address from wich command message was received.
186	 * @port the number of the port from wich command received.
187	 */
188	public TransactionHandler(JainMgcpStackImpl stack, InetAddress remoteAddress, int port) {
189		this(stack);
190		this.remoteAddress = remoteAddress;
191		this.remotePort = port;
192		if (this.stack.provider.getNotifiedEntity() == null) {
193			NotifiedEntity notifiedEntity = new NotifiedEntity(this.remoteAddress.getHostName(), this.remoteAddress
194					.getHostAddress(), this.remotePort);
195			this.stack.provider.setNotifiedEntity(notifiedEntity);
196		}
197	}
198
199	private void processTxTimeout() {
200		try {
201			// releases the tx
202			release();
203
204			// the try ensures the static timer will not get a runtime
205			// exception process tx timeout
206			commandEvent.setTransactionHandle(this.remoteTID);
207			if (sent) {				
208				stack.provider.processTxTimeout(commandEvent);
209			} else {
210				// TODO : Send back 406 TxTimedOut to NotifiedEntity
211				stack.provider.processRxTimeout(commandEvent);
212			}
213
214		} catch (Exception e) {
215			logger.error("Failed to release mgcp transaction localID=" + localTID, e);
216		}
217	}
218
219	private class LongtranTimerTask extends TimerTask {
220
221		public void run() {
222			if (logger.isDebugEnabled()) {
223				logger.debug("Transaction localID=" + localTID + " timeout");
224			}
225			processTxTimeout();
226		}
227	}
228
229	private class ReTransmissionTimerTask extends TimerTask {
230
231		public void run() {
232			try {
233				// Sending the command
234				countOfCommandRetransmitted++;
235
236				logger.warn("message = \n" + msgTemp + "\n local Tx ID = " + localTID + " Remote Tx ID = " + remoteTID
237						+ " Sending the Command " + countOfCommandRetransmitted);
238
239				stack.send(data, inetSocketAddress);
240				resetReTransmissionTimer();
241
242			} catch (Exception e) {
243				logger.error("Failed to release mgcp transaction localID=" + localTID, e);
244			}
245		}
246	}
247
248	private class THISTTimerTask extends TimerTask {
249
250		boolean responseSent = false;
251
252		THISTTimerTask(boolean responseSent) {
253			this.responseSent = responseSent;
254		}
255
256		public void run() {
257
258			if (!responseSent) {
259				if (logger.isDebugEnabled()) {
260					logger.debug("T-HIST timeout processTxTimeout ");
261				}
262				try {
263					processTxTimeout();
264				} catch (Exception e) {
265					logger.error("Failed to delete the jainMgcpResponseEvent for txId", e);
266				}
267			} else {
268				Integer key = new Integer(remoteTID);
269				TransactionHandler obj = stack.getCompletedTransactions().remove(key);
270				if (logger.isDebugEnabled()) {
271					logger.debug("T-HIST timeout deleting Response for Tx = " + remoteTID + " Response = " + obj);
272				}
273				obj = null;
274			}
275		}
276	}
277
278	/**
279	 * Check whether the given return code is a provisional response.
280	 * 
281	 * @param rc
282	 *            the return code
283	 * @return true when the code is provisional
284	 */
285	private boolean isProvisional(ReturnCode rc) {
286		final int rval = rc.getValue();
287
288		return ((99 < rval) && (rval < 200));
289	}
290
291	/** Release this transaction and frees all allocated resources. */
292	protected void release() {
293		stack.getLocalTransactions().remove(Integer.valueOf(localTID));
294		stack.getRemoteTxToLocalTxMap().remove(Integer.valueOf(remoteTID));
295		cancelTHISTTimerTask();
296		cancelLongtranTimer();
297		cancelReTransmissionTimer();
298
299	}
300
301	/**
302	 * Returns the transaction handle sent from application to the MGCP provider.
303	 * 
304	 * @return the int value wich identifiers the transaction handle.
305	 */
306	public int getRemoteTID() {
307		return remoteTID;
308	}
309
310	/**
311	 * Returns the transaction handle sent from MGCP provider to listener.
312	 * 
313	 * @return the int value wich identifiers the transaction handle.
314	 */
315	public int getLocalTID() {
316		return localTID;
317	}
318
319	/**
320	 * Encodes command event object into MGCP command message.
321	 * 
322	 * All descendant classes should implement this method with accordance of the command type.
323	 * 
324	 * @param event
325	 *            the command event object.
326	 * @return the encoded MGCP message.
327	 */
328	public abstract String encode(JainMgcpCommandEvent event);
329
330	/**
331	 * Encodes response event object into MGCP response message.
332	 * 
333	 * All descendant classes should implement this method with accordance of the response type.
334	 * 
335	 * @param event
336	 *            the response event object.
337	 * @return the encoded MGCP message.
338	 */
339	public abstract String encode(JainMgcpResponseEvent event);
340
341	/**
342	 * Decodes MGCP command message into jain mgcp command event object.
343	 * 
344	 * All descendant classes should implement this method with accordance of the command type.
345	 * 
346	 * @param MGCP
347	 *            message
348	 * @return jain mgcp command event object.
349	 */
350	public abstract JainMgcpCommandEvent decodeCommand(final String msg) throws ParseException;
351
352	/**
353	 * Decodes MGCP response message into jain mgcp response event object.
354	 * 
355	 * All descendant classes should implement this method with accordance of the command type.
356	 * 
357	 * @param MGCP
358	 *            message
359	 * @return jain mgcp response event object.
360	 */
361	public abstract JainMgcpResponseEvent decodeResponse(String message) throws ParseException;
362
363	public abstract JainMgcpResponseEvent getProvisionalResponse();
364
365	public void run() {
366		if (isCommand) {
367			this.send(this.getCommandEvent());
368		} else {
369			this.send(this.getResponseEvent());
370		}
371
372	}
373
374	protected void sendProvisionalResponse() {
375		this.send(getProvisionalResponse());
376	}
377
378	/**
379	 * Sends MGCP command from the application to the endpoint specified in the message.
380	 * 
381	 * @param event
382	 *            the jain mgcp command event object.
383	 */
384	private void send(JainMgcpCommandEvent event) {
385
386		sent = true;
387
388		String host = "";
389		int port = 0;
390
391		switch (event.getObjectIdentifier()) {
392		case Constants.CMD_NOTIFY:
393			Notify notifyCommand = (Notify) event;
394			NotifiedEntity notifiedEntity = notifyCommand.getNotifiedEntity();
395			if (notifiedEntity == null) {
396				notifiedEntity = this.stack.provider.getNotifiedEntity();
397			}
398			port = notifiedEntity.getPortNumber();
399			// if (notifiedEntity.getLocalName() != null) {
400			// host = notifiedEntity.getLocalName() + "@";
401			// }
402			host += notifiedEntity.getDomainName();
403
404			break;
405
406		default:
407			// determite destination address and port to send request to
408			// from endpoint identifier parameter.
409			String domainName = event.getEndpointIdentifier().getDomainName();
410
411			// now checks does port number is specified in the domain name
412			// if port number is not specified use 2427 by default
413			int pos = domainName.indexOf(':');
414			if (pos > 0) {
415				port = Integer.parseInt(domainName.substring(pos + 1));
416				host = domainName.substring(0, pos);
417			} else {
418				port = 2427;
419				host = domainName;
420			}
421			break;
422		}
423
424		// construct the destination as InetAddress object
425		InetAddress address = null;
426		try {
427			address = InetAddress.getByName(host);
428		} catch (UnknownHostException e) {
429			throw new IllegalArgumentException("Unknown endpoint " + host);
430		}
431
432		// save this tx in stack and start timer
433		remoteTID = event.getTransactionHandle();
434		source = event.getSource();
435		event.setTransactionHandle(localTID);
436
437		// encode event object as MGCP command and send over UDP.
438		String msg = encode(event);
439
440		msgTemp = msg;
441
442		data = msg.getBytes();
443		inetSocketAddress = new InetSocketAddress(address, port);
444
445		resetReTransmissionTimer();
446		resetTHISTTimerTask(false);
447
448		if (logger.isDebugEnabled()) {
449			logger.debug("Send command event to " + address + ", message\n" + msg);
450		}
451		countOfCommandRetransmitted++;
452		stack.send(data, inetSocketAddress);
453		
454	}
455
456	/**
457	 * Sends MGCP response message from the application to the host from wich origination command was received.
458	 * 
459	 * @param event
460	 *            the jain mgcp response event object.
461	 */
462	private void send(JainMgcpResponseEvent event) {
463
464		cancelLongtranTimer();
465
466		// to send response we already should know the address and port
467		// number from which the original request was received
468		if (remoteAddress == null) {
469			throw new IllegalArgumentException("Unknown orinator address");
470		}
471
472		// restore the original transaction handle parameter
473		// and encode event objet into MGCP response message
474		event.setTransactionHandle(remoteTID);
475
476		// encode event object into MGCP response message
477		String msg = encode(event);
478
479		// send response message to the originator
480		data = msg.getBytes();
481		inetSocketAddress = new InetSocketAddress(remoteAddress, remotePort);
482
483		if (logger.isDebugEnabled()) {
484			logger.debug("--- TransactionHandler:" + this + " :LocalID=" + localTID + ", Send response event to "
485					+ remoteAddress + ":" + remotePort + ", message\n" + msg);
486		}
487		stack.send(data, inetSocketAddress);
488
489		/*
490		 * Just reset timer in case of provisional response. Otherwise, release tx.
491		 */
492		if (isProvisional(event.getReturnCode())) {
493			// reset timer.
494			resetLongtranTimer();
495		} else {
496			
497			release();
498			stack.getCompletedTransactions().put(Integer.valueOf(event.getTransactionHandle()), this);
499			resetTHISTTimerTask(true);
500		}
501
502	}
503
504	private void cancelLongtranTimer() {
505		if (longtranTimerTask != null) {
506			longtranTimerTask.cancel();
507			longtranTimerTask = null;
508		}
509	}
510
511	private void resetLongtranTimer() {
512
513		longtranTimerTask = new LongtranTimerTask();
514		transactionHandlerTimer.schedule(longtranTimerTask, LONGTRAN_TIMER_TIMEOUT);
515	}
516
517	private void cancelReTransmissionTimer() {
518		if (reTransmissionTimer != null) {
519			reTransmissionTimer.cancel();
520			reTransmissionTimer = null;
521		}
522	}
523
524	private void resetReTransmissionTimer() {
525		cancelReTransmissionTimer();
526		reTransmissionTimer = new ReTransmissionTimerTask();
527		transactionHandlerTimer.schedule(reTransmissionTimer, calculateReTransmissionTimeout());
528	}
529
530	// TODO : Implement the AAD and ADEV from TCP
531	private int calculateReTransmissionTimeout() {
532		int reTransmissionTimeoutSec = A + N * D;
533		N = N * 2;
534		return reTransmissionTimeoutSec * 1000;
535	}
536
537	private void cancelTHISTTimerTask() {
538		if (tHISTTimerTask != null) {
539			tHISTTimerTask.cancel();
540			tHISTTimerTask = null;
541		}
542	}
543
544	private void resetTHISTTimerTask(boolean responseSent) {
545		cancelTHISTTimerTask();
546		tHISTTimerTask = new THISTTimerTask(responseSent);
547		transactionHandlerTimer.schedule(tHISTTimerTask, THIST_TIMER_TIMEOUT);
548	}
549
550	/**
551	 * constructs the object source for a command
552	 * 
553	 * @param tid
554	 * @return
555	 */
556	protected Object getObjectSource(int tid) {
557		if (sent) {
558			return stack;
559		} else {
560			return new ReceivedTransactionID(tid, this.remoteAddress, remotePort);
561		}
562	}
563
564	public boolean isCommand() {
565		return isCommand;
566	}
567
568	public void setCommand(boolean isCommand) {
569		this.isCommand = isCommand;
570	}
571
572	private JainMgcpCommandEvent getCommandEvent() {
573		return commandEvent;
574	}
575
576	public void setCommandEvent(JainMgcpCommandEvent commandEvent) {
577
578		this.commandEvent = commandEvent;
579		// this.actionToPerform.add(new ScheduleCommandSend());
580	}
581
582	private JainMgcpResponseEvent getResponseEvent() {
583		return responseEvent;
584	}
585
586	public void setResponseEvent(JainMgcpResponseEvent responseEvent) {
587
588		this.responseEvent = responseEvent;
589		// this.actionToPerform.add(new ScheduleCommandSend());
590
591	}
592
593	public void markRetransmision() {
594		this.retransmision = true;
595
596	}
597
598	public void receiveRequest(final EndpointIdentifier endpoint, final String msg, final Integer remoteTID) {
599
600		this.remoteTID = remoteTID;
601		this.endpoint = endpoint;
602		try {
603			commandEvent = decodeCommand(msg);
604			// if (logger.isDebugEnabled()) {
605			// logger.debug("Event decoded: \n" + event);
606			// }
607		} catch (ParseException e) {
608			logger.error("Coud not parse message: ", e);
609			return;
610		}
611		sent = false;
612
613		// store original transaction handle parameter
614		// and populate with local value
615
616		stack.getRemoteTxToLocalTxMap().put(remoteTID, new Integer(localTID));
617
618		commandEvent.setTransactionHandle(localTID);
619
620		resetLongtranTimer();
621
622		stack.provider.processMgcpCommandEvent(commandEvent);
623
624		// this.actionToPerform.add(new ScheduleRequestReceival(this));
625		// we shoudl be scheduled by message handler
626
627	}
628
629	/**
630	 * Used by stack for relaying received MGCP response messages to the application.
631	 * 
632	 * @param message
633	 *            receive MGCP response message.
634	 */
635	public void receiveResponse(String message) {
636
637		cancelReTransmissionTimer();
638		cancelLongtranTimer();
639
640		JainMgcpResponseEvent event = null;
641
642		try {
643			event = decodeResponse(message);
644		} catch (Exception e) {
645			logger.error("Could not decode message: ", e);
646		}
647
648		// restore original transaction handle parameter
649		event.setTransactionHandle(remoteTID);
650
651		/*
652		 * Just reset timer in case of provisional response. Otherwise, release tx.
653		 */
654		if (this.isProvisional(event.getReturnCode())) {
655			resetLongtranTimer();
656		}
657
658		stack.provider.processMgcpResponseEvent(event, commandEvent);
659
660		if (!this.isProvisional(event.getReturnCode())) {
661			this.release();
662		}
663	}
664
665	public String getEndpointId() {
666
667		return this.commandEvent.getEndpointIdentifier().toString();
668	}
669}