001    /*
002     * File:    $HeadURL: https://jvoicexml.svn.sourceforge.net/svnroot/jvoicexml/core/trunk/org.jvoicexml.implementation.mrcpv2/src/org/jvoicexml/callmanager/mrcpv2/SipCallManager.java $
003     * Version: $LastChangedRevision: 2727 $
004     * Date:    $Date: 2011-06-24 14:29:54 +0200 (Fr, 24 Jun 2011) $
005     * Author:  $LastChangedBy: schnelle $
006     *
007     * JVoiceXML - A free VoiceXML implementation.
008     *
009     * Copyright (C) 2009-2011 JVoiceXML group - http://jvoicexml.sourceforge.net
010     *
011     * This library is free software; you can redistribute it and/or
012     * modify it under the terms of the GNU Library General Public
013     * License as published by the Free Software Foundation; either
014     * version 2 of the License, or (at your option) any later version.
015     *
016     * This library is distributed in the hope that it will be useful,
017     * but WITHOUT ANY WARRANTY; without even the implied warranty of
018     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     * Library General Public License for more details.
020     *
021     * You should have received a copy of the GNU Library General Public
022     * License along with this library; if not, write to the Free Software
023     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024     *
025     */
026    package org.jvoicexml.callmanager.mrcpv2;
027    
028    import java.io.IOException;
029    import java.net.URI;
030    import java.util.Map;
031    
032    import javax.sip.Dialog;
033    import javax.sip.ObjectInUseException;
034    import javax.sip.SipException;
035    import javax.sip.address.Address;
036    
037    import org.apache.log4j.Logger;
038    import org.jvoicexml.CallManager;
039    import org.jvoicexml.JVoiceXml;
040    import org.jvoicexml.Session;
041    import org.jvoicexml.SessionListener;
042    import org.jvoicexml.client.mrcpv2.Mrcpv2ConnectionInformation;
043    import org.jvoicexml.event.ErrorEvent;
044    import org.jvoicexml.event.error.NoresourceError;
045    import org.mrcp4j.client.MrcpChannel;
046    import org.mrcp4j.client.MrcpInvocationException;
047    import org.speechforge.cairo.client.NoMediaControlChannelException;
048    import org.speechforge.cairo.client.SpeechClient;
049    import org.speechforge.cairo.client.SpeechClientImpl;
050    import org.speechforge.cairo.client.cloudimpl.SpeechCloudClient;
051    import org.speechforge.cairo.rtp.server.RTPStreamReplicator;
052    import org.speechforge.cairo.sip.SipSession;
053    import org.speechforge.zanzibar.sip.SipServer;
054    import org.speechforge.zanzibar.speechlet.SpeechletService;
055    import org.speechforge.zanzibar.telephony.TelephonyClient;
056    
057    import com.spokentech.speechdown.client.rtp.RtpTransmitter;
058    
059    /**
060     * A SIP call manager.
061     * @author Spencer Lord
062     * @author Dirk Schnelle-Walka
063     * @version $Revision: 2727 $
064     * @since 0.7.3
065     */
066    public final class SipCallManager
067        implements CallManager, SpeechletService, SessionListener {
068        /** Logger instance. */
069        private static final Logger LOGGER =
070            Logger.getLogger(SipCallManager.class);
071    
072        //TODO Better management (clean out orphaned sessions, or leases/timeouts)
073        /** Map of sessions. */
074        private  Map<String, SipCallManagerSession> sessions;
075    
076        //TODO make the ids the same.  Perhaps set the voicexml session id with
077        //sip id (rather than have it create its own UUID) or maybe there 
078        // is a way to attach the voicexml id to the sip session...
079        /** Map of sip id's to voicexml session ids. **/
080        private  Map<String, String> ids;
081     
082        /** Map of terminal names associated to an application. */
083        private Map<String, String> applications;
084    
085        /** The local SIP server. */
086        private SipServer sipServer;
087    
088        /** The URL of the cloud. */
089        private String cloudUrl;
090    
091        /** Reference to JVoiceXML. */
092        private JVoiceXml jvxml;
093    
094        /**
095         * @return the cloudUrl
096         */
097        public String getCloudUrl() {
098            return cloudUrl;
099        }
100    
101    
102        /**
103         * @param url the cloudUrl to set
104         */
105        public void setCloudUrl(final String url) {
106            cloudUrl = url;
107        }
108    
109    
110        /**
111         * Sets the SIP server.
112         * @param server the sipServer to set
113         */
114        public void setSipServer(final SipServer server) {
115            sipServer = server;
116        }
117    
118        /**
119         * Sets the configured applications.
120         * @param apps the configured applications
121         */
122        public void setApplications(final Map<String, String> apps) {
123            applications = apps;
124            LOGGER.info("loaded applications:");
125            for (String id : applications.keySet()) {
126                final String url = applications.get(id);
127                LOGGER.info(" - " + id + ": " + url);
128            }
129        }
130    
131        /**
132         * {@inheritDoc}
133         * TODO Rename this method to "stopDialog"  Need to change the interface
134         * first in the thirdparty jar.  
135         */
136        @Override
137        public void StopDialog(final SipSession pbxSession) throws SipException {
138            final String id = pbxSession.getId();
139            cleanupSession(id);
140        }
141    
142        /**
143         *  Cleanup the session after the call ended.
144         * @param id the session id.
145         */
146        private void cleanupSession(final String id) {
147    
148            final SipCallManagerSession session = sessions.get(id);
149            if (session == null) {
150                LOGGER.error("no session given. unable to cleanup session");
151                return;
152            }
153            session.getJvxmlSession().hangup();
154    
155            try {
156                //need to check for null mrcp session (in speechcloud case it
157                //will be null)
158                final SipSession mrcpsession = session.getMrcpSession();
159                if (mrcpsession != null) {
160                    mrcpsession.bye();
161                }
162                final SipSession pbxsession = session.getPbxSession();
163                pbxsession.bye();
164                final SpeechClient client = session.getSpeechClient();
165                client.stopActiveRecognitionRequests();
166                client.shutdown();
167            } catch (MrcpInvocationException e) {
168                LOGGER.error(e.getMessage(), e);
169            } catch (IOException e) {
170                LOGGER.error(e.getMessage(), e);
171            } catch (InterruptedException e) {
172                LOGGER.error(e.getMessage(), e);
173            } catch (NoMediaControlChannelException e) {
174                LOGGER.error(e.getMessage(), e);
175            } catch (SipException e) {
176                LOGGER.error(e.getMessage(), e);
177            } 
178            
179            //TODO Clean up after telephony client?
180            //session.getTelephonyClient();
181    
182            //remove the session from the map
183            sessions.remove(id);
184        }
185    
186        /**
187         * {@inheritDoc}
188         */
189        @Override
190        public void dtmf(final SipSession session, final char dtmf) {
191            // TODO Auto-generated method stub
192            
193        }
194    
195        /**
196         * {@inheritDoc}
197         */
198        @Override
199        public void startNewMrcpDialog(final SipSession pbxSession,
200                final SipSession mrcpSession) throws Exception {
201                final MrcpChannel ttsChannel = mrcpSession.getTtsChannel();
202                final MrcpChannel asrChannel = mrcpSession.getRecogChannel();
203                final SpeechClient speechClient =
204                    new SpeechClientImpl(ttsChannel, asrChannel);
205                final TelephonyClient telephonyClient = null;
206                    //new TelephonyClientImpl(pbxSession.getChannelName());
207                final Dialog dialog = pbxSession.getSipDialog();
208                final Address localParty = dialog.getLocalParty();
209                final String displayName = localParty.getDisplayName();
210                final String calledNumber;
211                //separate the scheme and port from the address
212                if ((displayName == null) || displayName.startsWith("sip:")) {
213                    final String uri = localParty.getURI().toString();
214                    String[] parts = uri.split(":");
215                    // get the first part of the address, which is the number that
216                    // was called.
217                    String[] parts2 = parts[1].split("@");  
218                    calledNumber = parts2[0];
219                } else {
220                    calledNumber = displayName;
221                }
222                final String applicationUri = applications.get(calledNumber);
223    
224                //use the number for looking up the application
225                LOGGER.info("called number: '" + calledNumber + "'");
226                LOGGER.info("calling application '" + applicationUri + "'...");
227                      
228                // Create a session (so we can get other signals from the caller)
229                // and release resources upon call completion
230                final String id = pbxSession.getId();
231                final SipCallManagerSession session =
232                    new SipCallManagerSession(id, pbxSession, mrcpSession,
233                            speechClient, telephonyClient);
234                try {
235                    final Address remoteParty = dialog.getRemoteParty();
236                    final String callingNumber = remoteParty.getURI().toString();
237                    final URI calledDevice = new URI(calledNumber);
238                    final URI callingDevice = new URI(callingNumber);
239                    final Mrcpv2ConnectionInformation remote =
240                        new Mrcpv2ConnectionInformation(callingDevice,
241                                calledDevice);
242                    remote.setTtsClient(speechClient);
243                    remote.setAsrClient(speechClient);
244    
245                    // Create a jvoicxml session and initiate a call at JVoiceXML.
246                    final Session jsession = jvxml.createSession(remote);
247                    
248                    //add a listener to capture the end of voicexml session event
249                    jsession.addSessionListener(this);
250                    
251                    //add the jvoicexml session to the session bag
252                    session.setJvxmlSession(jsession);
253                    synchronized (sessions) {
254                        sessions.put(id, session);
255                    }
256                    
257                    //workaround to deal with two id's
258                    //maps the voicexml sessionid to sip session id 
259                    //needed for case when the voicxml session ends before a hang up
260                    // and need to get to close the sip session
261                    synchronized (ids) {
262                       ids.put(jsession.getSessionID(), id);
263                    }
264    
265                    //start the application
266                    final URI uri = new URI(applicationUri);
267                    jsession.call(uri);
268                }  catch (Exception e) {
269                    LOGGER.error(e.getMessage(), e);
270                    throw e;
271                } catch (ErrorEvent e) {
272                    LOGGER.error(e.getMessage(), e);
273                    throw new Exception(e);
274                }
275        }
276    
277        /**
278         * {@inheritDoc}
279         */
280        @Override
281        public void startNewCloudDialog(final SipSession pbxSession,
282                final RTPStreamReplicator rtpReplicator,
283                final RtpTransmitter rtpTransmitter) throws Exception {
284            final SpeechClient speechClient =
285                new SpeechCloudClient(rtpReplicator, rtpTransmitter, cloudUrl);
286            TelephonyClient telephonyClient = null;
287                //new TelephonyClientImpl(pbxSession.getChannelName());
288            final Dialog dialog = pbxSession.getSipDialog();
289            final Address localParty = dialog.getLocalParty();  
290            final String applicationUri = applications.get(
291                    localParty.getDisplayName());
292            final String displayName = localParty.getDisplayName();
293            final String calledNumber;
294            //separate the scheme and port from the address
295            if ((displayName == null) || displayName.startsWith("sip:")) {
296                final String uri = localParty.getURI().toString();
297                String[] parts = uri.split(":");
298                // get the first part of the address, which is the number that
299                // was called.
300                String[] parts2 = parts[1].split("@");  
301                calledNumber = parts2[0];
302            } else {
303                calledNumber = displayName;
304            }
305    
306            // Create a session (so we can get other signals from the caller)
307            // and release resources upon call completion
308            String id = pbxSession.getId();
309            SipCallManagerSession session =
310                new SipCallManagerSession(id, pbxSession, null, speechClient,
311                        telephonyClient);
312    
313            try {
314                final Address remoteParty = dialog.getRemoteParty();
315                final String callingNumber = remoteParty.getURI().toString();
316                final URI calledDevice = new URI(calledNumber);
317                final URI callingDevice = new URI(callingNumber);
318                final Mrcpv2ConnectionInformation remote =
319                    new Mrcpv2ConnectionInformation(callingDevice, calledDevice);
320                remote.setTtsClient(speechClient);
321                remote.setAsrClient(speechClient);
322    
323                // Create a session and initiate a call at JVoiceXML.
324                final Session jsession = getJVoiceXml().createSession(remote);
325                
326                //add a listener to capture the end of voicexml session event
327                jsession.addSessionListener(this);
328                
329                //start the application
330                final URI uri = new URI(applicationUri);
331                jsession.call(uri);
332                
333                //add the jvoicexml session to the session bag
334                session.setJvxmlSession(jsession);
335                synchronized (sessions) {
336                    sessions.put(id, session);
337                }
338                
339                //workaround to deal with two id's
340                //maps the voicexml sessionid to sip session id 
341                //needed for case when the voicxml session ends before a hang up and
342                // need to get to close the sip session
343                synchronized (ids) {
344                   ids.put(session.getId(), id);
345                }
346    
347            }  catch (Exception e) {
348                LOGGER.error(e.getMessage(), e);
349                throw e;
350            } catch (ErrorEvent e) {
351                LOGGER.error(e.getMessage(), e);
352                throw new Exception(e);
353            }
354        }
355    
356        /**
357         * Retrieves the reference to the interpreter.
358         * @return the interpreter
359         */
360        public JVoiceXml getJVoiceXml() {
361            return jvxml;
362        }
363    
364        /**
365         * {@inheritDoc}
366         */
367        @Override
368        public void setJVoiceXml(final JVoiceXml jvoicexml) {
369           jvxml = jvoicexml;
370        }
371    
372        
373        // TODO startup/shutdown are in the DialogManagerInterface
374        // and start/stop are in the CallManager Interface -- don't need both sets.
375     
376        /**
377         * {@inheritDoc}
378         */
379        @Override
380        public void startup() {
381        }
382        
383        /**
384         * {@inheritDoc}
385         */
386        @Override
387        public void shutdown() {
388            LOGGER.info("shutdown mrcp sip callManager");
389        }
390        
391    
392        /**
393         * {@inheritDoc}
394         */
395        @Override
396        public void start() throws NoresourceError, IOException {
397            LOGGER.info("startup mrcp sip callManager");
398            sessions = new java.util.HashMap<String, SipCallManagerSession>();
399            ids = new java.util.HashMap<String, String>();
400        }
401    
402        /**
403         * {@inheritDoc}
404         */
405        @Override
406        public void stop() {
407            try {
408                sipServer.shutdown();
409            } catch (ObjectInUseException e) {
410                if (LOGGER.isDebugEnabled()) {
411                    LOGGER.debug(e.getLocalizedMessage(), e);
412                }
413            }
414        }
415    
416        /**
417         * {@inheritDoc}
418         */
419        @Override
420        public void sessionStarted(final Session session) {
421        }
422    
423        /**
424         * {@inheritDoc}
425         */
426        @Override
427        public void sessionEnded(final Session session) {
428            String id = session.getSessionID();
429            //workaround to deal with two id's
430            //maps the voicexml sessionid to sip session id 
431            //needed for case when the voicxml session ends before a hang up and
432            // need to get to close the sip session
433            
434            // get the sip sesison id
435            
436            //remove the session id mapping
437            final String sipId;
438            synchronized (ids) {
439                sipId = ids.get(id);
440                ids.remove(id);
441            }
442            
443            //clean up the session
444            cleanupSession(sipId);
445        }
446    }