/** * Start time:14:39:50 2008-11-21<br> * Project: mobicents-media-server-controllers<br> * * @author <a href="mailto:baranowb@gmail.com">baranowb - Bartosz Baranowski * </a> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> */ package org.mobicents.mgcp.stack; import jain.protocol.ip.mgcp.JainMgcpCommandEvent; import jain.protocol.ip.mgcp.JainMgcpResponseEvent; import jain.protocol.ip.mgcp.message.CreateConnection; import jain.protocol.ip.mgcp.message.CreateConnectionResponse; import jain.protocol.ip.mgcp.message.DeleteConnection; import jain.protocol.ip.mgcp.message.DeleteConnectionResponse; import jain.protocol.ip.mgcp.message.ModifyConnection; import jain.protocol.ip.mgcp.message.ModifyConnectionResponse; import jain.protocol.ip.mgcp.message.NotificationRequest; import jain.protocol.ip.mgcp.message.NotificationRequestResponse; import jain.protocol.ip.mgcp.message.parms.ConnectionIdentifier; import jain.protocol.ip.mgcp.message.parms.EndpointIdentifier; import jain.protocol.ip.mgcp.message.parms.NotifiedEntity; import jain.protocol.ip.mgcp.message.parms.RequestedEvent; import java.rmi.server.UID; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.log4j.Logger; import org.mobicents.mgcp.stack.handlers.EndpointHandlerManager; import org.mobicents.mgcp.stack.handlers.TransactionHandlerManagement; /** * Start time:14:39:50 2008-11-21<br> * Project: mobicents-media-server-controllers<br> * * @author <a href="mailto:baranowb@gmail.com">baranowb - Bartosz Baranowski * </a> */ public class EndpointHandler { protected static final Logger logger = Logger.getLogger(EndpointHandler.class); protected TreeSet<ConnectionIdentifier> connectionIds = new TreeSet<ConnectionIdentifier>( new Comparator<ConnectionIdentifier>() { public int compare(ConnectionIdentifier o1, ConnectionIdentifier o2) { if (o1 == null) return -1; if (o2 == null) return 1; return o1.toString().compareTo(o2.toString()); } }); /** * EndpointWide notification requests */ // protected Set<String> subscribedEvents = new HashSet<String>(); // protected BlockingQueue<Runnable> ourQueue = new // LinkedBlockingQueue<Runnable>(); // protected ThreadPoolQueueExecutor executor = new // ThreadPoolQueueExecutor(1, // 1, ourQueue); protected ThreadPoolQueueExecutor executor = null; protected List<TransactionHandlerManagement> ongoingTransactions = new ArrayList<TransactionHandlerManagement>(); protected EndpointHandlerManager stack = null; protected String endpointId = null; protected String fakeId = null; protected boolean useFakeId = false; private EndpointHandlerFactory factory = null; protected Set<RequestedEvent> requestedEvents = new TreeSet<RequestedEvent>(new Comparator<RequestedEvent>() { public int compare(RequestedEvent o1, RequestedEvent o2) { if (o1 == null) return -1; if (o2 == null) return 1; return o1.toString().compareTo(o2.toString()); } }); public EndpointHandler(EndpointHandlerManager jainMgcpStackImpl, EndpointHandlerFactory factory){ this.stack = jainMgcpStackImpl; this.factory = factory; } public void init(String endpointId){ this.endpointId = endpointId; this.fakeId = new UID().toString(); this.executor = this.stack.getNextExecutor(); } // public EndpointHandler(EndpointHandlerManager jainMgcpStackImpl, String endpointId) { // this.endpointId = endpointId; // this.stack = jainMgcpStackImpl; // this.fakeId = new UID().toString(); // this.executor = this.stack.getNextExecutor(); // } public String getFakeId() { return fakeId; } /** * Method called once tx has been created * * @param handler */ public void addTransactionHandler(TransactionHandlerManagement handler) { this.ongoingTransactions.add(handler); handler.setEndpointHandler(this); } public void scheduleTransactionHandler(TransactionHandlerManagement th) { this.executor.execute(th); } /** * Should be called when reqeust command is delivered - either sent or * received. * * @param commandEvent * @param handler */ public void commandDelivered(JainMgcpCommandEvent commandEvent, TransactionHandlerManagement handler) { // FIXME: Is there any real check we have to do here? // doEndChecks(); } /** * Should be called when response command is delivered - either sent or * received.<br> * This method should take care of initializing another endpoint handler for * cases like CRCX with wildcard and actual name of endpoint in response * * @param commandEvent * @param event * @param handler */ public void commandDelivered(JainMgcpCommandEvent commandEvent, JainMgcpResponseEvent event, TransactionHandlerManagement handler) { if (commandEvent instanceof CreateConnection) { CreateConnection ccRequest = (CreateConnection) commandEvent; CreateConnectionResponse ccResponse = (CreateConnectionResponse) event; int responseCode = ccResponse.getReturnCode().getValue(); MgcpResponseType type = MgcpResponseType.getResponseTypeFromCode(responseCode); // This is local/we have to handle switch (type) { case SuccessResponse: EndpointIdentifier specificEndpointId = ccResponse.getSpecificEndpointIdentifier(); if (specificEndpointId != null && this.endpointId.compareTo(specificEndpointId.toString()) == 0) { // On Success we have to add ConnectionId, possibly process // NotificationRequest like, since it can have // NotificationRequest embeded :] this.connectionIds.add(ccResponse.getConnectionIdentifier()); if (ccRequest.getNotificationRequestParms() != null) { processRequestedEvents(ccRequest.getNotifiedEntity(), ccRequest.getNotificationRequestParms() .getRequestedEvents()); } } else if (isAnyOfWildcard(endpointId)) { // This means that client asked to get connection on any of // endpoints, we have to change mapping // this.ongoingTransactions.remove(handler); // EndpointHandler validEndpointHandler = this.stack // .getEndpointHandler(specificEndpointId.toString()); this.endpointId = specificEndpointId.toString(); EndpointHandler concurrent = this.stack.switchMapping(this.fakeId, specificEndpointId.toString()); if (concurrent != null) { //Let us switch back to fakeId so that doEndChecks() will remove this EH this.endpointId = this.fakeId; // This could mean that in a mean while someone created // this, we should use it. this.ongoingTransactions.remove(handler); concurrent.addTransactionHandler(handler); concurrent.commandDelivered(commandEvent, event, handler); } else { this.commandDelivered(commandEvent, event, handler); } } else { // ? This is error, this means that endpoitID does not match // and // possibly is AllOfWildcad - *, which is not permited logger.error("Wrong endpoitn id, local: " + endpointId + ", fakeId: " + fakeId + ", id in response: " + specificEndpointId); } break; case TransientError: case PermanentError: // Remove handler from ongoing transaction and cleaning will // happen TransactionHandler.release(true) is called default: break; } } else if (commandEvent instanceof NotificationRequest) { // FIXME: there can be wildcard notficatoion request = allof ?? // what should we do than? NotificationRequest nRequest = (NotificationRequest) commandEvent; NotificationRequestResponse nrResponse = (NotificationRequestResponse) event; int responseCode = nrResponse.getReturnCode().getValue(); MgcpResponseType type = MgcpResponseType.getResponseTypeFromCode(responseCode); switch (type) { case ProvisionalResponse: return; case SuccessResponse: if (this.endpointId.equals(nRequest.getEndpointIdentifier().toString())) { // On success we have to add subscription info processRequestedEvents(nRequest.getNotifiedEntity(), nRequest.getRequestedEvents()); } else if (isWildCardEndpointName(nRequest.getEndpointIdentifier().toString())) { // FIXME: do nothing? Or should we create wildcard named EH? // Its // not mentioned - but this should be also } break; case TransientError: case PermanentError: default: break; } } else if (commandEvent instanceof ModifyConnection) { // FIXME: there can be wildcard notficatoion request = allof ?? // what should we do than? ModifyConnection mcRequest = (ModifyConnection) commandEvent; ModifyConnectionResponse mcResponse = (ModifyConnectionResponse) event; int responseCode = mcResponse.getReturnCode().getValue(); MgcpResponseType type = MgcpResponseType.getResponseTypeFromCode(responseCode); switch (type) { case ProvisionalResponse: return; case SuccessResponse: if (this.endpointId.equals(mcRequest.getEndpointIdentifier().toString())) { // On success we have to check for embeded NR // processRequestedEvents(mcRequest.getNotifiedEntity(), // mcRequest.getRequestedEvents()); if (mcRequest.getNotificationRequestParms() != null) { processRequestedEvents(mcRequest.getNotifiedEntity(), mcRequest.getNotificationRequestParms() .getRequestedEvents()); } } else { logger.error("Wrong EndpoiontId on " + event.getClass().getSimpleName() + " event. This should be set to valid EId, this EId: " + this.endpointId); } break; case TransientError: case PermanentError: default: break; } } else if (commandEvent instanceof DeleteConnection) { // FIXME: there can be wildcard notficatoion request = allof ?? // what should we do than? DeleteConnection dcRequest = (DeleteConnection) commandEvent; DeleteConnectionResponse dcResponse = (DeleteConnectionResponse) event; int responseCode = dcResponse.getReturnCode().getValue(); MgcpResponseType type = MgcpResponseType.getResponseTypeFromCode(responseCode); switch (type) { case ProvisionalResponse: return; case SuccessResponse: if (this.endpointId.equals(dcRequest.getEndpointIdentifier().toString())) { // On success we have to: // * check for embeded NR // * delete connection // * delete all connections // processRequestedEvents(mcRequest.getNotifiedEntity(), // mcRequest.getRequestedEvents()); // http://tools.ietf.org/html/rfc3435#section-2.3.7 if (dcRequest.getNotificationRequestParms() != null) { processRequestedEvents(null, dcRequest.getNotificationRequestParms().getRequestedEvents()); } // http://tools.ietf.org/html/rfc3435#section-2.3.7 or // http://tools.ietf.org/html/rfc3435#section-2.3.8 if (dcRequest.getConnectionIdentifier() != null) { // deletes specific connection this.connectionIds.remove(dcRequest.getConnectionIdentifier()); if (logger.isDebugEnabled()) { logger.debug("Removing connection:" + dcRequest.getConnectionIdentifier() + " From:" + Arrays.toString(this.connectionIds.toArray()) + " ------ " + this); } } else { // 2.3.9 this.connectionIds.clear(); } } else { logger.error("Wrong EndpoiontId on " + event.getClass().getSimpleName() + " event. This should be set to valid EId, this EId: " + this.endpointId); } break; case TransientError: case PermanentError: default: break; } } doEndChecks(); } protected void processRequestedEvents(NotifiedEntity entity, RequestedEvent[] rEvents) { // FIXME: there is only single list of those? New one overwrites // previous? // RequestedEvents is not incremental list, it acts as setter? this.requestedEvents.clear(); if (rEvents != null) { for (RequestedEvent re : rEvents) { this.requestedEvents.add(re); } } } /** * Called when localy initiated transaction times out * * @param commandEvent * @param transactionHandler */ public void processTxTimeout(JainMgcpCommandEvent commandEvent, TransactionHandlerManagement transactionHandler) { transactionHandler.clearEndpointHandler(); doEndChecks(); } /** * Called when remotely created tx times out * * @param commandEvent * @param transactionHandler */ public void processRxTimeout(JainMgcpCommandEvent commandEvent, TransactionHandlerManagement transactionHandler) { // TODO Auto-generated method stub transactionHandler.clearEndpointHandler(); doEndChecks(); } public void transactionHandlerDeleted(TransactionHandlerManagement th) { this.ongoingTransactions.remove(th); th.setEndpointHandler(null); doEndChecks(); } /** * Should be called in case something changes - connection list change, * subscription list change, tx termination/completition */ public void doEndChecks() { if (this.connectionIds.size() == 0 && this.ongoingTransactions.size() == 0 // && this.requestedEvents.size() == 0 // FIXME: This can cause a leak if someone does not unregister // Oleg, Amit? ) { try { if (isWildCardEndpointName(this.endpointId)) { this.stack.removeEndpointHandler(this.fakeId); } else { this.stack.removeEndpointHandler(this.endpointId); } } finally { this.factory.deallocate(this); // We have a pool now, we dont kill them :} // this.executor.shutdownNow(); } } } // For now simple detection, no range wildcard detection public static boolean isWildCardEndpointName(String endpointId) { return isAnyOfWildcard(endpointId) || isAllOfWildcard(endpointId); } public static boolean isAnyOfWildcard(String endpointId) { boolean flag = endpointId.contains("$");; return flag; } public static boolean isAllOfWildcard(String endpointId) { return endpointId.contains("*"); } public String toString() { return this.getClass().getSimpleName() + this.hashCode() + " - EId: " + this.endpointId + ", Subscribed events: " + Arrays.toString(this.requestedEvents.toArray()) + ", connectionIds: " + Arrays.toString(this.connectionIds.toArray()) + ", Handlers: " + Arrays.toString(this.ongoingTransactions.toArray()); } public void setUseFake(boolean b) { this.useFakeId = b; } }