1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sf.asterisk.manager;
18
19 import java.io.IOException;
20 import java.io.Serializable;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28
29 import net.sf.asterisk.AsteriskVersion;
30 import net.sf.asterisk.io.SocketConnectionFacade;
31 import net.sf.asterisk.io.impl.SocketConnectionFacadeImpl;
32 import net.sf.asterisk.manager.action.ChallengeAction;
33 import net.sf.asterisk.manager.action.CommandAction;
34 import net.sf.asterisk.manager.action.EventGeneratingAction;
35 import net.sf.asterisk.manager.action.LoginAction;
36 import net.sf.asterisk.manager.action.LogoffAction;
37 import net.sf.asterisk.manager.action.ManagerAction;
38 import net.sf.asterisk.manager.event.ConnectEvent;
39 import net.sf.asterisk.manager.event.DisconnectEvent;
40 import net.sf.asterisk.manager.event.ManagerEvent;
41 import net.sf.asterisk.manager.event.ResponseEvent;
42 import net.sf.asterisk.manager.impl.ManagerReaderImpl;
43 import net.sf.asterisk.manager.impl.ManagerWriterImpl;
44 import net.sf.asterisk.manager.impl.ResponseEventsImpl;
45 import net.sf.asterisk.manager.impl.Util;
46 import net.sf.asterisk.manager.response.ChallengeResponse;
47 import net.sf.asterisk.manager.response.CommandResponse;
48 import net.sf.asterisk.manager.response.ManagerError;
49 import net.sf.asterisk.manager.response.ManagerResponse;
50 import net.sf.asterisk.util.Log;
51 import net.sf.asterisk.util.LogFactory;
52
53 /***
54 * Default implemention of the ManagerConnection interface.<br>
55 * Generelly avoid direct use of this class. Use the ManagerConnectionFactory to
56 * obtain a ManagerConnection instead.<br>
57 * When using a dependency injection framework like Spring direct usage for
58 * wiring up beans that require a ManagerConnection property is fine though.<br>
59 * Note that the DefaultManagerConnection will create one new Thread
60 * for reading data from Asterisk on the first call to on of the login()
61 * methods.
62 *
63 * @see net.sf.asterisk.manager.ManagerConnectionFactory
64 * @author srt
65 * @version $Id: DefaultManagerConnection.java,v 1.34 2005/11/08 15:25:18 srt Exp $
66 */
67 public class DefaultManagerConnection implements ManagerConnection, Dispatcher
68 {
69 /***
70 * Instance logger.
71 */
72 private final Log logger = LogFactory.getLog(getClass());
73
74 /***
75 * Used to construct the internalActionId.
76 */
77 private long actionIdCount = 0;
78
79
80 /***
81 * The Asterisk server to connect to.
82 */
83 private AsteriskServer asteriskServer;
84
85 /***
86 * The username to use for login as defined in Asterisk's
87 * <code>manager.conf</code>.
88 */
89 protected String username;
90
91 /***
92 * The password to use for login as defined in Asterisk's
93 * <code>manager.conf</code>.
94 */
95 protected String password;
96
97 /***
98 * The default timeout to wait for a ManagerResponse after sending a
99 * ManagerAction.
100 */
101 private long defaultResponseTimeout = 2000;
102
103 /***
104 * The default timeout to wait for the last ResponseEvent after sending an
105 * EventGeneratingAction.
106 */
107 private long defaultEventTimeout = 5000;
108
109 /***
110 * The timeout to use when connecting the the Asterisk server.
111 */
112 private int socketTimeout = 0;
113
114 /***
115 * The time the calling thread is sleeping between checking if a reponse or
116 * the protocol identifer has been received.
117 */
118 private long sleepTime = 50;
119
120 /***
121 * Should we continue to reconnect after an authentication failure?
122 */
123 private boolean keepAliveAfterAuthenticationFailure = false;
124
125 /***
126 * The socket to use for TCP/IP communication with Asterisk.
127 */
128 private SocketConnectionFacade socket;
129
130 /***
131 * The thread that runs the reader.
132 */
133 private Thread readerThread;
134
135 /***
136 * The reader to use to receive events and responses from asterisk.
137 */
138 private ManagerReader reader;
139
140 /***
141 * The writer to use to send actions to asterisk.
142 */
143 private ManagerWriter writer;
144
145 /***
146 * The protocol identifer Asterisk sends on connect.
147 */
148 private String protocolIdentifier;
149
150 /***
151 * The version of the Asterisk server we are connected to.
152 */
153 private AsteriskVersion version;
154
155 /***
156 * Contains the registered handlers that process the ManagerResponses.<br>
157 * Key is the internalActionId of the Action sent and value the
158 * corresponding ResponseHandler.
159 */
160 private final Map responseHandlers;
161
162 /***
163 * Contains the event handlers that handle ResponseEvents for the
164 * sendEventGeneratingAction methods.<br>
165 * Key is the internalActionId of the Action sent and value the
166 * corresponding EventHandler.
167 */
168 private final Map responseEventHandlers;
169
170 /***
171 * Contains the event handlers that users registered.
172 */
173 private final List eventHandlers;
174
175 /***
176 * Should we attempt to reconnect when the connection is lost?<br>
177 * This is set to <code>true</code> after successful login and to
178 * <code>false</code> after logoff or after an authentication failure when
179 * keepAliveAfterAuthenticationFailure is <code>false</code>.
180 */
181 protected boolean keepAlive = false;
182
183 /***
184 * Creates a new instance.
185 */
186 public DefaultManagerConnection()
187 {
188 this.asteriskServer = new AsteriskServer();
189
190 this.responseHandlers = new HashMap();
191 this.responseEventHandlers = new HashMap();
192 this.eventHandlers = new ArrayList();
193 }
194
195 /***
196 * Creates a new instance with the given connection parameters.
197 *
198 * @param hostname the hosname of the Asterisk server to connect to.
199 * @param port the port where Asterisk listens for incoming Manager API
200 * connections, usually 5038.
201 * @param username the username to use for login
202 * @param password the password to use for login
203 */
204 public DefaultManagerConnection(String hostname, int port, String username,
205 String password)
206 {
207 this();
208
209 setHostname(hostname);
210 setPort(port);
211 setUsername(username);
212 setPassword(password);
213 }
214
215
216
217 protected ManagerReader createReader(Dispatcher dispatcher,
218 AsteriskServer server)
219 {
220 return new ManagerReaderImpl(dispatcher, server);
221 }
222
223 protected ManagerWriter createWriter()
224 {
225 return new ManagerWriterImpl();
226 }
227
228 /***
229 * Sets the hostname of the asterisk server to connect to.<br>
230 * Default is <code>localhost</code>.
231 *
232 * @param hostname the hostname to connect to
233 */
234 public void setHostname(String hostname)
235 {
236 this.asteriskServer.setHostname(hostname);
237 }
238
239 /***
240 * Sets the port to use to connect to the asterisk server. This is the port
241 * specified in asterisk's <code>manager.conf</code> file.<br>
242 * Default is 5038.
243 *
244 * @param port the port to connect to
245 */
246 public void setPort(int port)
247 {
248 this.asteriskServer.setPort(port);
249 }
250
251 /***
252 * Sets the username to use to connect to the asterisk server. This is the
253 * username specified in asterisk's <code>manager.conf</code> file.
254 *
255 * @param username the username to use for login
256 */
257 public void setUsername(String username)
258 {
259 this.username = username;
260 }
261
262 /***
263 * Sets the password to use to connect to the asterisk server. This is the
264 * password specified in asterisk's <code>manager.conf</code> file.
265 *
266 * @param password the password to use for login
267 */
268 public void setPassword(String password)
269 {
270 this.password = password;
271 }
272
273 /***
274 * Sets the time in milliseconds the synchronous sendAction methods
275 * {@link #sendAction(ManagerAction)} will wait for a response before
276 * throwing a TimeoutException.<br>
277 * Default is 2000.
278 *
279 * @param defaultTimeout default timeout in milliseconds
280 * @deprecated use {@link #setDefaultResponseTimeout(long)} instead
281 */
282 public void setDefaultTimeout(long defaultTimeout)
283 {
284 setDefaultResponseTimeout(defaultTimeout);
285 }
286
287 /***
288 * Sets the time in milliseconds the synchronous method
289 * {@link #sendAction(ManagerAction)} will wait for a response before
290 * throwing a TimeoutException.<br>
291 * Default is 2000.
292 *
293 * @param defaultResponseTimeout default response timeout in milliseconds
294 * @since 0.2
295 */
296 public void setDefaultResponseTimeout(long defaultResponseTimeout)
297 {
298 this.defaultResponseTimeout = defaultResponseTimeout;
299 }
300
301 /***
302 * Sets the time in milliseconds the synchronous method
303 * {@link #sendEventGeneratingAction(EventGeneratingAction)} will wait for a
304 * response and the last response event before throwing a TimeoutException.<br>
305 * Default is 5000.
306 *
307 * @param defaultEventTimeout default event timeout in milliseconds
308 * @since 0.2
309 */
310 public void setDefaultEventTimeout(long defaultEventTimeout)
311 {
312 this.defaultEventTimeout = defaultEventTimeout;
313 }
314
315 /***
316 * Sets the time in milliseconds the synchronous methods
317 * {@link #sendAction(ManagerAction)} and
318 * {@link #sendAction(ManagerAction, long)} will sleep between two checks
319 * for the arrival of a response. This value should be rather small.<br>
320 * The sleepTime attribute is also used when checking for the protocol
321 * identifer.<br>
322 * Default is 50.
323 *
324 * @param sleepTime time in milliseconds to sleep between two checks for the
325 * arrival of a response or the protocol identifier
326 * @deprecated this has been replaced by an interrupt based response
327 * checking approach.
328 */
329 public void setSleepTime(long sleepTime)
330 {
331 this.sleepTime = sleepTime;
332 }
333
334 /***
335 * Set to <code>true</code> to try reconnecting to ther asterisk serve
336 * even if the reconnection attempt threw an AuthenticationFailedException.<br>
337 * Default is <code>false</code>.
338 */
339 public void setKeepAliveAfterAuthenticationFailure(
340 boolean keepAliveAfterAuthenticationFailure)
341 {
342 this.keepAliveAfterAuthenticationFailure = keepAliveAfterAuthenticationFailure;
343 }
344
345
346
347 public void registerUserEventClass(Class userEventClass)
348 {
349 if (reader == null)
350 {
351 reader = createReader(this, asteriskServer);
352 }
353
354 reader.registerEventClass(userEventClass);
355 }
356
357 public void setSocketTimeout(int socketTimeout)
358 {
359 this.socketTimeout = socketTimeout;
360 }
361
362 /***
363 * Logs in to the asterisk manager using asterisk's MD5 based
364 * challenge/response protocol. The login is delayed until the protocol
365 * identifier has been received by the reader.
366 *
367 * @throws AuthenticationFailedException if the username and/or password are
368 * incorrect
369 * @throws TimeoutException if no response is received within the specified
370 * timeout period
371 * @see ChallengeAction
372 * @see LoginAction
373 */
374 public void login() throws IOException, AuthenticationFailedException,
375 TimeoutException
376 {
377 login(defaultResponseTimeout);
378 }
379
380 /***
381 * Does the real login, following the steps outlined below.<br>
382 * <ol>
383 * <li>Connects to the asterisk server by calling {@link #connect()} if not
384 * already connected
385 * <li>Waits until the protocol identifier is received. This is checked
386 * every {@link #sleepTime} ms but not longer than timeout ms in total.
387 * <li>Sends a {@link ChallengeAction} requesting a challenge for authType
388 * MD5.
389 * <li>When the {@link ChallengeResponse} is received a {@link LoginAction}
390 * is sent using the calculated key (MD5 hash of the password appended to
391 * the received challenge).
392 * </ol>
393 *
394 * @param timeout the maximum time to wait for the protocol identifier (in
395 * ms)
396 * @throws AuthenticationFailedException if username or password are
397 * incorrect and the login action returns an error or if the MD5
398 * hash cannot be computed. The connection is closed in this
399 * case.
400 * @throws TimeoutException if a timeout occurs either while waiting for the
401 * protocol identifier or when sending the challenge or login
402 * action. The connection is closed in this case.
403 */
404 private void login(long timeout) throws IOException,
405 AuthenticationFailedException, TimeoutException
406 {
407 long start;
408 long timeSpent;
409 ChallengeAction challengeAction;
410 ChallengeResponse challengeResponse;
411 String challenge;
412 String key;
413 LoginAction loginAction;
414 ManagerResponse loginResponse;
415
416 if (socket == null)
417 {
418 connect();
419 }
420
421 start = System.currentTimeMillis();
422 while (getProtocolIdentifier() == null)
423 {
424 try
425 {
426 Thread.sleep(sleepTime);
427 }
428 catch (InterruptedException e)
429 {
430 }
431
432 timeSpent = System.currentTimeMillis() - start;
433 if (getProtocolIdentifier() == null && timeSpent > timeout)
434 {
435 disconnect();
436 throw new TimeoutException(
437 "Timeout waiting for protocol identifier");
438 }
439 }
440
441 challengeAction = new ChallengeAction("MD5");
442 challengeResponse = (ChallengeResponse) sendAction(challengeAction);
443
444 challenge = challengeResponse.getChallenge();
445
446 try
447 {
448 MessageDigest md;
449
450 md = MessageDigest.getInstance("MD5");
451 if (challenge != null)
452 {
453 md.update(challenge.getBytes());
454 }
455 if (password != null)
456 {
457 md.update(password.getBytes());
458 }
459 key = Util.toHexString(md.digest());
460 }
461 catch (NoSuchAlgorithmException ex)
462 {
463 disconnect();
464 throw new AuthenticationFailedException(
465 "Unable to create login key using MD5 Message Digest", ex);
466 }
467
468 loginAction = new LoginAction(username, "MD5", key);
469 loginResponse = sendAction(loginAction);
470 if (loginResponse instanceof ManagerError)
471 {
472 disconnect();
473 throw new AuthenticationFailedException(loginResponse.getMessage());
474 }
475
476
477
478 this.keepAlive = true;
479
480 logger.info("Successfully logged in");
481
482 this.version = determineVersion();
483 this.writer.setTargetVersion(version);
484
485 logger.info("Determined Asterisk version: " + version);
486 }
487
488 protected AsteriskVersion determineVersion() throws IOException, TimeoutException
489 {
490 ManagerResponse showVersionFilesResponse;
491
492
493 showVersionFilesResponse = sendAction(new CommandAction("show version files"),
494 defaultResponseTimeout * 2);
495 if (showVersionFilesResponse instanceof CommandResponse)
496 {
497 List showVersionFilesResult;
498
499 showVersionFilesResult = ((CommandResponse) showVersionFilesResponse).getResult();
500 if (showVersionFilesResult != null && showVersionFilesResult.size() > 0)
501 {
502 String line1;
503
504 line1 = (String) showVersionFilesResult.get(0);
505 if (line1 != null && line1.startsWith("File"))
506 {
507 return AsteriskVersion.ASTERISK_1_2;
508 }
509 }
510 }
511
512 return AsteriskVersion.ASTERISK_1_0;
513 }
514
515 protected synchronized void connect() throws IOException
516 {
517 logger.info("Connecting to " + asteriskServer.getHostname() + " port "
518 + asteriskServer.getPort());
519
520 if (this.reader == null)
521 {
522 this.reader = createReader(this, asteriskServer);
523 }
524
525 if (this.writer == null)
526 {
527 this.writer = createWriter();
528 }
529
530 this.socket = createSocket();
531
532 this.reader.setSocket(socket);
533 this.readerThread = new Thread(reader, "ManagerReader");
534 this.readerThread.start();
535
536 this.writer.setSocket(socket);
537 }
538
539 protected SocketConnectionFacade createSocket() throws IOException
540 {
541 return new SocketConnectionFacadeImpl(asteriskServer.getHostname(),
542 asteriskServer.getPort(), socketTimeout);
543 }
544
545 /***
546 * Returns <code>true</code> if there is a socket connection to the
547 * asterisk server, <code>false</code> otherwise.
548 *
549 * @return <code>true</code> if there is a socket connection to the
550 * asterisk server, <code>false</code> otherwise.
551 */
552 public synchronized boolean isConnected()
553 {
554 return socket != null && socket.isConnected();
555
556 }
557
558 /***
559 * Sends a {@link LogoffAction} and disconnects from the server.
560 */
561 public synchronized void logoff() throws IOException, TimeoutException
562 {
563 LogoffAction logoffAction;
564
565
566 this.keepAlive = false;
567
568 logoffAction = new LogoffAction();
569
570 if (socket != null)
571 {
572 sendAction(logoffAction);
573 disconnect();
574 }
575 }
576
577 /***
578 * Closes the socket connection.
579 */
580 private synchronized void disconnect()
581 {
582 if (this.socket != null)
583 {
584 logger.info("Closing socket.");
585 try
586 {
587 this.socket.close();
588 }
589 catch (IOException ex)
590 {
591 logger.warn("Unable to close socket: " + ex.getMessage());
592 }
593 this.socket = null;
594 }
595 }
596
597 public ManagerResponse sendAction(ManagerAction action) throws IOException,
598 TimeoutException, IllegalArgumentException, IllegalStateException
599 {
600 return sendAction(action, defaultResponseTimeout);
601 }
602
603 public ManagerResponse sendAction(ManagerAction action, long timeout)
604 throws IOException, TimeoutException, IllegalArgumentException,
605 IllegalStateException
606 {
607 long start;
608 long timeSpent;
609 ResponseHandlerResult result;
610 ManagerResponseHandler callbackHandler;
611
612 result = new ResponseHandlerResult();
613 callbackHandler = new DefaultResponseHandler(result, Thread
614 .currentThread());
615
616 sendAction(action, callbackHandler);
617
618 start = System.currentTimeMillis();
619 timeSpent = 0;
620 while (result.getResponse() == null)
621 {
622 try
623 {
624 Thread.sleep(timeout - timeSpent);
625 }
626 catch (InterruptedException ex)
627 {
628 }
629
630
631 timeSpent = System.currentTimeMillis() - start;
632 if (result.getResponse() == null && timeSpent > timeout)
633 {
634 throw new TimeoutException("Timeout waiting for response to "
635 + action.getAction());
636 }
637 }
638
639 return result.getResponse();
640 }
641
642 public void sendAction(ManagerAction action,
643 ManagerResponseHandler callbackHandler) throws IOException,
644 IllegalArgumentException, IllegalStateException
645 {
646 String internalActionId;
647
648 if (action == null)
649 {
650 throw new IllegalArgumentException(
651 "Unable to send action: action is null.");
652 }
653
654 if (socket == null)
655 {
656 throw new IllegalStateException("Unable to send "
657 + action.getAction() + " action: not connected.");
658 }
659
660 internalActionId = createInternalActionId();
661
662
663
664 if (callbackHandler != null)
665 {
666 synchronized (this.responseHandlers)
667 {
668 this.responseHandlers.put(internalActionId, callbackHandler);
669 }
670 }
671
672 writer.sendAction(action, internalActionId);
673 }
674
675 public ResponseEvents sendEventGeneratingAction(EventGeneratingAction action)
676 throws IOException, EventTimeoutException,
677 IllegalArgumentException, IllegalStateException
678 {
679 return sendEventGeneratingAction(action, defaultEventTimeout);
680 }
681
682 public ResponseEvents sendEventGeneratingAction(
683 EventGeneratingAction action, long timeout) throws IOException,
684 EventTimeoutException, IllegalArgumentException,
685 IllegalStateException
686 {
687 ResponseEventsImpl responseEvents;
688 ResponseEventHandler responseEventHandler;
689 String internalActionId;
690 long start;
691 long timeSpent;
692
693 if (action == null)
694 {
695 throw new IllegalArgumentException(
696 "Unable to send action: action is null.");
697 }
698 else if (action.getActionCompleteEventClass() == null)
699 {
700 throw new IllegalArgumentException(
701 "Unable to send action: actionCompleteEventClass is null.");
702 }
703 else if (!ResponseEvent.class.isAssignableFrom(action
704 .getActionCompleteEventClass()))
705 {
706 throw new IllegalArgumentException(
707 "Unable to send action: actionCompleteEventClass is not a ResponseEvent.");
708 }
709
710 if (socket == null)
711 {
712 throw new IllegalStateException("Unable to send "
713 + action.getAction() + " action: not connected.");
714 }
715
716 responseEvents = new ResponseEventsImpl();
717 responseEventHandler = new ResponseEventHandler(responseEvents, action
718 .getActionCompleteEventClass(), Thread.currentThread());
719
720 internalActionId = createInternalActionId();
721
722
723 synchronized (this.responseHandlers)
724 {
725 this.responseHandlers.put(internalActionId, responseEventHandler);
726 }
727
728
729 synchronized (this.responseEventHandlers)
730 {
731 this.responseEventHandlers.put(internalActionId,
732 responseEventHandler);
733 }
734
735 writer.sendAction(action, internalActionId);
736
737
738 start = System.currentTimeMillis();
739 timeSpent = 0;
740 while (responseEvents.getResponse() == null
741 || !responseEvents.isComplete())
742 {
743 try
744 {
745 Thread.sleep(timeout - timeSpent);
746 }
747 catch (InterruptedException ex)
748 {
749 }
750
751
752 timeSpent = System.currentTimeMillis() - start;
753 if ((responseEvents.getResponse() == null || !responseEvents
754 .isComplete())
755 && timeSpent > timeout)
756 {
757
758 synchronized (this.responseEventHandlers)
759 {
760 this.responseEventHandlers.remove(internalActionId);
761 }
762
763 throw new EventTimeoutException(
764 "Timeout waiting for response or response events to "
765 + action.getAction(), responseEvents);
766 }
767 }
768
769
770
771 synchronized (this.responseEventHandlers)
772 {
773 this.responseEventHandlers.remove(internalActionId);
774 }
775
776 return responseEvents;
777 }
778
779 /***
780 * Creates a new unique internal action id based on the hash code of this
781 * connection and a sequence.
782 *
783 * @see Util#addInternalActionId(String, String)
784 * @see Util#getInternalActionId(String)
785 * @see Util#stripInternalActionId(String)
786 */
787 private String createInternalActionId()
788 {
789 final StringBuffer sb;
790
791 sb = new StringBuffer();
792 sb.append(this.hashCode());
793 sb.append("_");
794 sb.append(this.actionIdCount++);
795
796 return sb.toString();
797 }
798
799 public void addEventHandler(final ManagerEventHandler eventHandler)
800 {
801 synchronized (this.eventHandlers)
802 {
803
804 if (!this.eventHandlers.contains(eventHandler))
805 {
806 this.eventHandlers.add(eventHandler);
807 }
808 }
809 }
810
811 public void removeEventHandler(final ManagerEventHandler eventHandler)
812 {
813 synchronized (this.eventHandlers)
814 {
815 if (this.eventHandlers.contains(eventHandler))
816 {
817 this.eventHandlers.remove(eventHandler);
818 }
819 }
820 }
821
822 public String getProtocolIdentifier()
823 {
824 return this.protocolIdentifier;
825 }
826
827 public AsteriskServer getAsteriskServer()
828 {
829 return asteriskServer;
830 }
831
832
833
834 /***
835 * This method is called by the reader whenever a {@link ManagerResponse} is
836 * received. The response is dispatched to the associated
837 * {@link ManagerResponseHandler}.
838 *
839 * @param response the response received by the reader
840 * @see ManagerReader
841 */
842 public void dispatchResponse(ManagerResponse response)
843 {
844 final String actionId;
845 String internalActionId;
846 ManagerResponseHandler responseHandler;
847
848
849 if (response == null)
850 {
851 logger.error("Unable to dispatch null response");
852 return;
853 }
854
855 actionId = response.getActionId();
856 internalActionId = null;
857 responseHandler = null;
858
859 if (actionId != null)
860 {
861 internalActionId = Util.getInternalActionId(actionId);
862 response.setActionId(Util.stripInternalActionId(actionId));
863 }
864
865 logger.debug("Dispatching response with internalActionId '"
866 + internalActionId + "':\n" + response);
867
868 if (internalActionId != null)
869 {
870 synchronized (this.responseHandlers)
871 {
872 responseHandler = (ManagerResponseHandler) this.responseHandlers
873 .get(internalActionId);
874 if (responseHandler != null)
875 {
876 this.responseHandlers.remove(internalActionId);
877 }
878 else
879 {
880
881
882 logger.debug("No response handler registered for "
883 + "internalActionId '" + internalActionId + "'");
884 }
885 }
886 }
887 else
888 {
889 logger.error("Unable to retrieve internalActionId from response: "
890 + "actionId '" + actionId + "':\n" + response);
891 }
892
893 if (responseHandler != null)
894 {
895 try
896 {
897 responseHandler.handleResponse(response);
898 }
899 catch (RuntimeException e)
900 {
901 logger.warn("Unexpected exception in responseHandler "
902 + responseHandler.getClass().getName(), e);
903 }
904 }
905 }
906
907 /***
908 * This method is called by the reader whenever a ManagerEvent is received.
909 * The event is dispatched to all registered ManagerEventHandlers.
910 *
911 * @param event the event received by the reader
912 * @see #addEventHandler(ManagerEventHandler)
913 * @see #removeEventHandler(ManagerEventHandler)
914 * @see ManagerReader
915 */
916 public void dispatchEvent(ManagerEvent event)
917 {
918
919 if (event == null)
920 {
921 logger.error("Unable to dispatch null event");
922 return;
923 }
924
925 logger.debug("Dispatching event:\n" + event.toString());
926
927
928 if (event instanceof ResponseEvent)
929 {
930 ResponseEvent responseEvent;
931 String internalActionId;
932
933 responseEvent = (ResponseEvent) event;
934 internalActionId = responseEvent.getInternalActionId();
935 if (internalActionId != null)
936 {
937 synchronized (responseEventHandlers)
938 {
939 ManagerEventHandler eventHandler;
940
941 eventHandler = (ManagerEventHandler) responseEventHandlers
942 .get(internalActionId);
943 if (eventHandler != null)
944 {
945 try
946 {
947 eventHandler.handleEvent(event);
948 }
949 catch (RuntimeException e)
950 {
951 logger.warn("Unexpected exception in eventHandler "
952 + eventHandler.getClass().getName(), e);
953 }
954 }
955 }
956 }
957 else
958 {
959 logger.error("Unable to handle ResponseEvent without "
960 + "internalActionId:\n" + responseEvent);
961 }
962 }
963
964
965 synchronized (eventHandlers)
966 {
967 Iterator i = eventHandlers.iterator();
968 while (i.hasNext())
969 {
970 ManagerEventHandler eventHandler;
971
972 eventHandler = (ManagerEventHandler) i.next();
973 try
974 {
975 eventHandler.handleEvent(event);
976 }
977 catch (RuntimeException e)
978 {
979 logger.warn("Unexpected exception in eventHandler "
980 + eventHandler.getClass().getName(), e);
981 }
982 }
983 }
984
985
986 if (event instanceof ConnectEvent)
987 {
988 ConnectEvent connectEvent;
989 String protocolIdentifier;
990
991 connectEvent = (ConnectEvent) event;
992 protocolIdentifier = connectEvent.getProtocolIdentifier();
993 setProtocolIdentifier(protocolIdentifier);
994 }
995 else if (event instanceof DisconnectEvent)
996 {
997 reconnect();
998 }
999 }
1000
1001 /***
1002 * This method is called when a {@link ConnectEvent} is received from the
1003 * reader. Having received a correct protocol identifier is the precodition
1004 * for logging in.
1005 *
1006 * @param protocolIdentifier the protocol version used by the Asterisk
1007 * server.
1008 */
1009 private void setProtocolIdentifier(final String protocolIdentifier)
1010 {
1011 logger.info("Connected via " + protocolIdentifier);
1012
1013 if (!"Asterisk Call Manager/1.0".equals(protocolIdentifier))
1014 {
1015 logger.warn("Unsupported protocol version '" + protocolIdentifier
1016 + "'. Use at your own risk.");
1017 }
1018
1019 this.protocolIdentifier = protocolIdentifier;
1020
1021 }
1022
1023 /***
1024 * Reconnects to the asterisk server when the connection is lost.<br>
1025 * While keepAlive is <code>true</code> we will try to reconnect.
1026 * Reconnection attempts will be stopped when the {@link #logoff()} method
1027 * is called or when the login after a successful reconnect results in an
1028 * {@link AuthenticationFailedException} suggesting that the manager
1029 * credentials have changed and keepAliveAfterAuthenticationFailure is not
1030 * set.<br>
1031 * This method is called when a {@link DisconnectEvent} is received from the
1032 * reader.
1033 */
1034 private void reconnect()
1035 {
1036 int numTries;
1037
1038
1039 disconnect();
1040
1041
1042 numTries = 0;
1043 while (this.keepAlive)
1044 {
1045 try
1046 {
1047 if (numTries < 10)
1048 {
1049
1050
1051 Thread.sleep(50);
1052 }
1053 else
1054 {
1055
1056
1057 Thread.sleep(5000);
1058 }
1059 }
1060 catch (InterruptedException e1)
1061 {
1062
1063 }
1064
1065 try
1066 {
1067 connect();
1068
1069 try
1070 {
1071 login();
1072 logger.info("Successfully reconnected.");
1073
1074 break;
1075 }
1076 catch (AuthenticationFailedException e1)
1077 {
1078 if (this.keepAliveAfterAuthenticationFailure)
1079 {
1080 logger.error("Unable to log in after reconnect.");
1081 }
1082 else
1083 {
1084 logger.error("Unable to log in after reconnect. "
1085 + "Giving up.");
1086 this.keepAlive = false;
1087 }
1088 }
1089 catch (TimeoutException e1)
1090 {
1091
1092 logger.error("TimeoutException while trying to log in "
1093 + "after reconnect.");
1094 synchronized (this)
1095 {
1096 socket.close();
1097 }
1098 }
1099 }
1100 catch (IOException e)
1101 {
1102
1103
1104 logger.warn("Exception while trying to reconnect: "
1105 + e.getMessage());
1106 }
1107 numTries++;
1108 }
1109 }
1110
1111
1112
1113 /***
1114 * A simple data object to store a ManagerResult.
1115 */
1116 private class ResponseHandlerResult implements Serializable
1117 {
1118 /***
1119 * Serializable version identifier
1120 */
1121 private static final long serialVersionUID = 7831097958568769220L;
1122 private ManagerResponse response;
1123
1124 public ResponseHandlerResult()
1125 {
1126 }
1127
1128 public ManagerResponse getResponse()
1129 {
1130 return this.response;
1131 }
1132
1133 public void setResponse(ManagerResponse response)
1134 {
1135 this.response = response;
1136 }
1137 }
1138
1139 /***
1140 * A simple response handler that stores the received response in a
1141 * ResponseHandlerResult for further processing.
1142 */
1143 private class DefaultResponseHandler
1144 implements
1145 ManagerResponseHandler,
1146 Serializable
1147 {
1148 /***
1149 * Serializable version identifier
1150 */
1151 private static final long serialVersionUID = 2926598671855316803L;
1152 private ResponseHandlerResult result;
1153 private final Thread thread;
1154
1155 /***
1156 * Creates a new instance.
1157 *
1158 * @param result the result to store the response in
1159 * @param thread the thread to interrupt when the response has been
1160 * received
1161 */
1162 public DefaultResponseHandler(ResponseHandlerResult result,
1163 Thread thread)
1164 {
1165 this.result = result;
1166 this.thread = thread;
1167 }
1168
1169 public void handleResponse(ManagerResponse response)
1170 {
1171 result.setResponse(response);
1172 thread.interrupt();
1173 }
1174 }
1175
1176 /***
1177 * A combinded event and response handler that adds received events and the
1178 * response to a ResponseEvents object.
1179 */
1180 private class ResponseEventHandler
1181 implements
1182 ManagerEventHandler,
1183 ManagerResponseHandler,
1184 Serializable
1185 {
1186 /***
1187 * Serializable version identifier
1188 */
1189 private static final long serialVersionUID = 2926598671855316803L;
1190 private final ResponseEventsImpl events;
1191 private final Class actionCompleteEventClass;
1192 private final Thread thread;
1193
1194 /***
1195 * Creates a new instance.
1196 *
1197 * @param events the ResponseEventsImpl to store the events in
1198 * @param actionCompleteEventClass the type of event that indicates that
1199 * all events have been received
1200 * @param thread the thread to interrupt when the
1201 * actionCompleteEventClass has been received
1202 */
1203 public ResponseEventHandler(ResponseEventsImpl events,
1204 Class actionCompleteEventClass, Thread thread)
1205 {
1206 this.events = events;
1207 this.actionCompleteEventClass = actionCompleteEventClass;
1208 this.thread = thread;
1209 }
1210
1211 public void handleEvent(ManagerEvent event)
1212 {
1213
1214 if (event instanceof ResponseEvent)
1215 {
1216 ResponseEvent responseEvent;
1217
1218 responseEvent = (ResponseEvent) event;
1219 events.addEvent(responseEvent);
1220 }
1221
1222
1223 if (actionCompleteEventClass.isAssignableFrom(event.getClass()))
1224 {
1225 synchronized (events)
1226 {
1227 events.setComplete(true);
1228 if (events.getResponse() != null)
1229 {
1230 thread.interrupt();
1231 }
1232 }
1233 }
1234 }
1235
1236 public void handleResponse(ManagerResponse response)
1237 {
1238 synchronized (events)
1239 {
1240 events.setRepsonse(response);
1241 if (response instanceof ManagerError)
1242 {
1243 events.setComplete(true);
1244 }
1245
1246
1247 if (events.isComplete())
1248 {
1249 thread.interrupt();
1250 }
1251 }
1252 }
1253 }
1254 }