package org.mobicents.ipbx.session.call; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.sip.AuthInfo; import javax.servlet.sip.SipFactory; import javax.servlet.sip.SipServletMessage; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import javax.servlet.sip.SipSession; import javax.servlet.sip.SipURI; import javax.servlet.sip.TelURL; import javax.servlet.sip.URI; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.core.Events; import org.jboss.seam.log.Log; import org.mobicents.ipbx.entity.Binding; import org.mobicents.ipbx.entity.CallState; import org.mobicents.ipbx.entity.PstnGatewayAccount; import org.mobicents.ipbx.entity.Registration; import org.mobicents.ipbx.session.CallAction; import org.mobicents.ipbx.session.call.model.CallParticipant; import org.mobicents.ipbx.session.call.model.CallParticipantManager; import org.mobicents.ipbx.session.call.model.Conference; import org.mobicents.ipbx.session.call.model.ConferenceManager; import org.mobicents.ipbx.session.call.model.WorkspaceStateManager; import org.mobicents.ipbx.session.configuration.PbxConfiguration; import org.mobicents.ipbx.session.security.SimpleSipAuthenticator; import org.mobicents.mscontrol.MsConnection; import org.mobicents.mscontrol.MsConnectionEvent; import org.mobicents.mscontrol.MsEndpoint; import org.mobicents.mscontrol.MsLinkEvent; import org.mobicents.mscontrol.MsLinkMode; import org.mobicents.mscontrol.MsSession; import org.mobicents.mscontrol.events.MsEventFactory; import org.mobicents.servlet.sip.seam.entrypoint.media.MediaController; import org.mobicents.servlet.sip.seam.media.framework.IVRHelper; import org.mobicents.servlet.sip.seam.media.framework.IVRHelperManager; import org.mobicents.servlet.sip.seam.media.framework.MediaEventDispatcher; import org.mobicents.servlet.sip.seam.media.framework.MediaSessionStore; @Name("pbxEventHandler") @Scope(ScopeType.STATELESS) public class PbxEventHandler { @Logger Log log; @In MsSession msSession; @In SipSession sipSession; @In MediaController mediaController; @In MediaEventDispatcher mediaEventDispatcher; @In MediaSessionStore mediaSessionStore; @In IVRHelper ivrHelper; @In(create=true) SimpleSipAuthenticator sipAuthenticator; @In SipFactory sipFactory; @In MsEventFactory eventFactory; @In(create=true) CallAction callAction; public static final String PR_JNDI_NAME = "media/trunk/PacketRelay/$"; @Observer("INVITE") public void doInvite(SipServletRequest request) { String fromUri = request.getFrom().getURI().toString(); String toUri = request.getTo().getURI().toString(); // Authentication just by user name for now Registration fromRegistration = sipAuthenticator.authenticate(fromUri); Registration toRegistration = sipAuthenticator.findRegistration(toUri); if(fromRegistration == null || toRegistration == null) { try { request.createResponse(404).send(); } catch (IOException e) { log.error("Can't send 404 response"); } return; } Binding toBinding = null; if(toRegistration.getBindings() != null) { if(toRegistration.getBindings().size() > 0) { toBinding = toRegistration.getBindings().iterator().next(); toUri = toBinding.getContactAddress(); } } String fromUser = fromRegistration.getUser().getName(); String toUser = toRegistration.getUser().getName(); // Ringing SipServletResponse sipServletResponse = request.createResponse(SipServletResponse.SC_RINGING); // Extract sdp Object sdpObj = null; try { sipServletResponse.send(); sdpObj = request.getContent(); } catch (IOException e) {} byte[] sdpBytes = (byte[]) sdpObj; String sdp = new String(sdpBytes); // Create call participants to join a call CallParticipant fromParticipant = CallParticipantManager.instance().getCallParticipant( fromUri); fromParticipant.setName(fromUser); fromParticipant.setRegistration(fromRegistration); fromParticipant.setUri(fromRegistration.getUri()); fromParticipant.setInitiator(true); CallParticipant toParticipant = CallParticipantManager.instance().getCallParticipant( toUri); toParticipant.setName(toUser); toParticipant.setRegistration(toRegistration); toParticipant.setUri(toUri); toParticipant.setBinding(toBinding); //Store the request/session in the registration fromParticipant.setInitialRequest(request); // Store reusable info for later in this session sipSession.setAttribute("participant", fromParticipant); sipSession.setAttribute("firstSipMessage", request); Conference conf = null; if(toParticipant.getConference() == null) { // If the callee is in a call, just call all his registered phones and join them to a new // conference where you can talk to him conf = ConferenceManager.instance().getNewConference(); fromParticipant.setConference(conf); toParticipant.setConference(conf); toParticipant.setCallState(CallState.CONNECTING); fromParticipant.setCallState(CallState.CONNECTING); WorkspaceStateManager.instance().getWorkspace(fromUser).setOutgoing(toParticipant); callAction.dialParticipant(toParticipant); log.info("Calling inactive user"); } else { // If the callee is in a call already we must ask to join that call conf = toParticipant.getConference(); fromParticipant.setConference(conf); fromParticipant.setCallState(CallState.ASKING); log.info("Calling an active user, we must ask for permission"); } WorkspaceStateManager.instance().getWorkspace(toUser).setIncoming(fromParticipant); mediaController.createConnection(PR_JNDI_NAME).modify("$", sdp); Events.instance().raiseEvent("incomingCall", request); } @Observer("RESPONSE") public void doResponse(SipServletResponse response) throws ServletException, IOException { if(response.getMethod().equalsIgnoreCase("INVITE")) { CallParticipant participant = (CallParticipant) sipSession.getAttribute("participant"); participant.setCallState(CallState.CONNECTING); // Just handle the response normally by updating the state int status = response.getStatus(); if(status == 180) { participant.setCallState(CallState.RINGING); } else if (status == 200) { Object sdpObj = response.getContent(); byte[] sdpBytes = (byte[]) sdpObj; String sdp = new String(sdpBytes); sipSession.setAttribute("firstSipMessage", response); mediaController.createConnection(PR_JNDI_NAME).modify("$", sdp); } else if(status == 401 || status == 407) { Integer authAttempts = (Integer) sipSession.getAttribute("authAttempts"); if(authAttempts == null) authAttempts = 0; sipSession.setAttribute("authAttempts", authAttempts++); // If it doesn't succeed 3 times give up on authentication if(authAttempts > 3) return; // So we dialed a PSTN phone. Use auth info from the settings PstnGatewayAccount account = participant.getPstnGatewayAccount(); if(account != null) { AuthInfo authInfo = sipFactory.createAuthInfo(); authInfo.addAuthInfo(response.getStatus(), response.getChallengeRealms().next(), account.getUsername(), account.getPassword()); SipServletRequest challengeRequest = response.getSession().createRequest( response.getRequest().getMethod()); challengeRequest.addAuthHeader(response, authInfo); challengeRequest.send(); } } else if(status >= 300){ quitConference(participant, participant.getConference()); } } } @Observer("BYE") public void doBye(SipServletRequest request) throws ServletException, IOException { log.info("Got BYE request:\n" + request); SipServletResponse sipServletResponse = request.createResponse(SipServletResponse.SC_OK); sipServletResponse.send(); // Clean up the mess in the models CallParticipant participant = (CallParticipant) sipSession.getAttribute("participant"); Conference conference = participant.getConference(); quitConference(participant, conference); } private void quitConference(CallParticipant participant, Conference conf) throws IOException { participant.setCallState(CallState.DISCONNECTED); participant.setConference(null); // Remove the call from the callee GUI WorkspaceStateManager.instance().getWorkspace(participant.getName()).removeCall(participant); CallParticipant[] callParticipants = conf.getParticipants(); log.info("number of participants left in the conference = #0", callParticipants.length); if(callParticipants.length == 1) { CallParticipant cp = callParticipants[0]; WorkspaceStateManager.instance().getWorkspace(cp.getName()).endCall(cp); } // Remove the call from other users' GUIs for(CallParticipant cp : callParticipants) { WorkspaceStateManager.instance().getWorkspace(cp.getName()).removeCall(participant); String disconnectedUsername = participant.getName(); // Determine if there are more call legs to phones registered under that user. If there are other // call legs, don't remove that call from user's workspace boolean remove = true; for(CallParticipant cp2 : callParticipants) { if(cp2.getName().equals(disconnectedUsername)) { remove = false; } } if(remove) WorkspaceStateManager.instance().getWorkspace(disconnectedUsername).removeCall(cp); } // Release media stuff if possible if(participant.getMsLink() != null) participant.getMsLink().release(); if(participant.getMsConnection() != null) participant.getMsConnection().release(); participant.setMsConnection(null); participant.setMsLink(null); } @Observer("connectionOpen") public void connectionOpen(MsConnectionEvent event) { SipServletMessage sipMessage = (SipServletMessage) sipSession.getAttribute("firstSipMessage"); if(sipMessage instanceof SipServletRequest) { SipServletRequest request = (SipServletRequest) sipMessage; connectionOpenRequest(event, request); } else { SipServletResponse response = (SipServletResponse) sipMessage; connectionOpenResponse(event, response); } } private void connectionOpenResponse(MsConnectionEvent event, SipServletResponse response) { CallParticipant participant = (CallParticipant) sipSession.getAttribute("participant"); MsConnection connection = event.getConnection(); participant.setMsConnection(connection); String sdp = event.getConnection().getLocalDescriptor(); SipServletRequest ack = response.createAck(); try { ack.setContent(sdp, "application/sdp"); ack.send(); } catch (Exception e) { log.error(e); } if(participant.getConference() != null) { mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join(participant.getConference().getEndpointName(), connection.getEndpoint().getLocalName()); System.out.println("RRRRRRRRRRRRRRRRRRRRRR Join " + participant.toString() + " in " + participant.getConference().getEndpointName()); } else { throw new IllegalStateException("Participant doesnt have conference assigned " + participant.toString()); } } private void connectionOpenRequest(MsConnectionEvent event, SipServletRequest inviteRequest) { CallParticipant participant = (CallParticipant) sipSession.getAttribute("participant"); MsConnection connection = event.getConnection(); participant.setMsConnection(connection); String sdp = event.getConnection().getLocalDescriptor(); SipServletResponse sipServletResponse = inviteRequest .createResponse(SipServletResponse.SC_OK); try { sipServletResponse.setContent(sdp, "application/sdp"); sipServletResponse.send(); } catch (Exception e) { log.error(e); } if(participant.getConference() != null && !CallState.ASKING.equals(participant.getCallState()) // Yeah, we must ask for permissing in this case ) { mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join(participant.getConference().getEndpointName(), connection.getEndpoint().getLocalName()); } } @Observer("linkConnected") public void doLinkConnected(MsLinkEvent event) { synchronized (PR_JNDI_NAME) { MsEndpoint endpoint = event.getSource().getEndpoints()[0]; CallParticipant participant = (CallParticipant) sipSession.getAttribute("participant"); log.info("Link connected for " + participant); mediaSessionStore.setMsEndpoint(endpoint); ivrHelper.detectDtmf(); participant.setCallState(CallState.CONNECTED); Conference conf = participant.getConference(); // We should upgrade the call state to "active" for all participants (most already have it) CallParticipant[] ps = conf.getParticipants(); for(CallParticipant p : ps) { if(p!=participant) { if(p.getCallState().equals(CallState.CONNECTED)) { p.setCallState(CallState.INCALL); try { endRingback(p); } catch (Exception e) {} participant.setCallState(CallState.INCALL); // Many of these are not needed, but it's hard to debug what is missing in corner cases WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(participant); WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(p); WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(participant); WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(p); } } if(p.getCallState().equals(CallState.INCALL)) { participant.setCallState(CallState.INCALL); // Many of these are not needed, but it's hard to debug what is missing in corner cases WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(p); WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(participant); WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(participant); WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(p); } } // And if this is the first participant in the conference, let's determine which endpoint we use if(participant != null) { participant.setMsLink(event.getSource()); if(conf.getEndpoint() == null) { conf.setEndpoint(endpoint); System.out.println("RRRRRRRRRRRRRRRRRRRR conf endpoint assigned - " + endpoint.getLocalName()); playRingback(participant); } } } } public void playRingback(CallParticipant participant) { String tone = PbxConfiguration.getProperty("pbx.default.ringback.tone"); log.info("playing following ringbacktone #0", tone); IVRHelperManager.instance().getIVRHelper(participant.getSipSession()).playAnnouncementWithDtmf(tone); } private void endRingback(CallParticipant participant) { IVRHelperManager.instance().getIVRHelper(participant.getSipSession()).detectDtmf(); } // Extract username from URI private String getUser(URI uri) { String user = null; if(uri.isSipURI()) { SipURI suri = (SipURI) uri; user = suri.getUser(); } else { TelURL turi = (TelURL) uri; user = turi.getPhoneNumber(); } return user; } @Observer("PUBLISH") public void doPublish(SipServletRequest request) throws ServletException, Exception { request.createResponse(200).send(); } @Observer("OPTIONS") public void doOptions(SipServletRequest request) throws ServletException, Exception { request.createResponse(200).send(); } @Observer("SUBSCRIBE") public void doSubscribe(SipServletRequest request) throws ServletException, Exception { SipServletResponse response = request.createResponse(200); response.addHeader("Expires", "200"); response.send(); } @Observer("INFO") public void doInfo(SipServletRequest request) throws ServletException, Exception { request.createResponse(200).send(); } }