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