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 }