View Javadoc

1   /*
2    *  Copyright 2004-2006 Stefan Reuter
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  package org.asteriskjava.live.internal;
18  
19  import java.util.ArrayList;
20  import java.util.Date;
21  import java.util.List;
22  
23  import org.asteriskjava.live.AsteriskChannel;
24  import org.asteriskjava.live.CallDetailRecord;
25  import org.asteriskjava.live.CallerId;
26  import org.asteriskjava.live.ChannelState;
27  import org.asteriskjava.live.ChannelStateHistoryEntry;
28  import org.asteriskjava.live.DialedChannelHistoryEntry;
29  import org.asteriskjava.live.Extension;
30  import org.asteriskjava.live.ExtensionHistoryEntry;
31  import org.asteriskjava.live.HangupCause;
32  import org.asteriskjava.live.LinkedChannelHistoryEntry;
33  import org.asteriskjava.live.ManagerCommunicationException;
34  import org.asteriskjava.live.NoSuchChannelException;
35  import org.asteriskjava.manager.action.AbsoluteTimeoutAction;
36  import org.asteriskjava.manager.action.ChangeMonitorAction;
37  import org.asteriskjava.manager.action.GetVarAction;
38  import org.asteriskjava.manager.action.HangupAction;
39  import org.asteriskjava.manager.action.MonitorAction;
40  import org.asteriskjava.manager.action.PauseMonitorAction;
41  import org.asteriskjava.manager.action.PlayDtmfAction;
42  import org.asteriskjava.manager.action.RedirectAction;
43  import org.asteriskjava.manager.action.SetVarAction;
44  import org.asteriskjava.manager.action.StopMonitorAction;
45  import org.asteriskjava.manager.action.UnpauseMonitorAction;
46  import org.asteriskjava.manager.response.ManagerError;
47  import org.asteriskjava.manager.response.ManagerResponse;
48  
49  /***
50   * Default implementation of the AsteriskChannel interface.
51   * 
52   * @author srt
53   * @version $Id: AsteriskChannelImpl.java 729 2007-05-26 05:16:57Z sprior $
54   */
55  class AsteriskChannelImpl extends AbstractLiveObject implements AsteriskChannel
56  {
57      private static final String CAUSE_VARIABLE_NAME = "PRI_CAUSE";
58  
59      /***
60       * Unique id of this channel.
61       */
62      private final String id;
63  
64      /***
65       * The traceId is used to trace originated channels.
66       */
67      private String traceId;
68  
69      /***
70       * Date this channel has been created.
71       */
72      private final Date dateOfCreation;
73  
74      /***
75       * Date this channel has left the Asterisk server.
76       */
77      private Date dateOfRemoval;
78  
79      /***
80       * Name of this channel.
81       */
82      private String name;
83  
84      /***
85       * Caller*ID of this channel.
86       */
87      private CallerId callerId;
88  
89      /***
90       * State of this channel.
91       */
92      private ChannelState state;
93  
94      /***
95       * Account code used to bill this channel.
96       */
97      private String account;
98      private final List<ExtensionHistoryEntry> extensionHistory;
99      private final List<ChannelStateHistoryEntry> stateHistory;
100     private final List<LinkedChannelHistoryEntry> linkedChannelHistory;
101     private final List<DialedChannelHistoryEntry> dialedChannelHistory;
102 
103     private AsteriskChannel dialedChannel;
104 
105     private AsteriskChannel dialingChannel;
106 
107     /***
108      * If this channel is bridged to another channel, the linkedChannel contains
109      * the channel this channel is bridged with.
110      */
111     private AsteriskChannel linkedChannel;
112 
113     /***
114      * Indicates if this channel was linked to another channel at least once.
115      */
116     private boolean wasLinked;
117 
118     private HangupCause hangupCause;
119 
120     private String hangupCauseText;
121 
122     private CallDetailRecordImpl callDetailRecord;
123 
124     /***
125      * MeetMe room user associated with this channel if any, <code>null</code>
126      * otherwise.
127      */
128     private MeetMeUserImpl meetMeUserImpl;
129 
130     /***
131      * Creates a new Channel.
132      *
133      * @param server server this channel belongs to.
134      * @param name name of this channel, for example "SIP/1310-20da".
135      * @param id unique id of this channel, for example "1099015093.165".
136      * @param dateOfCreation date this channel has been created.
137      * @throws NullPointerException if any of the parameters is null.
138      */
139     AsteriskChannelImpl(final AsteriskServerImpl server, final String name, final String id, final Date dateOfCreation)
140             throws NullPointerException
141     {
142         super(server);
143 
144         if (server == null || name == null || id == null || dateOfCreation == null)
145         {
146             throw new NullPointerException("Parameters passed to AsteriskChannelImpl() must not be null.");
147         }
148         this.name = name;
149         this.id = id;
150         this.dateOfCreation = dateOfCreation;
151         this.extensionHistory = new ArrayList<ExtensionHistoryEntry>();
152         this.stateHistory = new ArrayList<ChannelStateHistoryEntry>();
153         this.linkedChannelHistory = new ArrayList<LinkedChannelHistoryEntry>();
154         this.dialedChannelHistory = new ArrayList<DialedChannelHistoryEntry>();
155     }
156 
157     public String getId()
158     {
159         return id;
160     }
161 
162     String getTraceId()
163     {
164         return traceId;
165     }
166 
167     void setTraceId(String traceId)
168     {
169         this.traceId = traceId;
170     }
171 
172     public String getName()
173     {
174         return name;
175     }
176 
177     /***
178      * Changes the name of this channel.
179      * 
180      * @param date date of the name change.
181      * @param name the new name of this channel.
182      */
183     void nameChanged(Date date, String name)
184     {
185         final String oldName = this.name;
186 
187         if (oldName != null && oldName.equals(name))
188         {
189             return;
190         }
191 
192         this.name = name;
193         firePropertyChange(PROPERTY_NAME, oldName, name);
194     }
195 
196     public CallerId getCallerId()
197     {
198         return callerId;
199     }
200 
201     /***
202      * Sets the caller id of this channel.
203      * 
204      * @param callerId the caller id of this channel.
205      */
206     void setCallerId(final CallerId callerId)
207     {
208         final CallerId oldCallerId = this.callerId;
209 
210         this.callerId = callerId;
211         firePropertyChange(PROPERTY_CALLER_ID, oldCallerId, callerId);
212     }
213 
214     public ChannelState getState()
215     {
216         return state;
217     }
218 
219     public boolean wasInState(ChannelState state)
220     {
221         synchronized (stateHistory)
222         {
223             for (ChannelStateHistoryEntry historyEntry : stateHistory)
224             {
225                 if (historyEntry.getState() == state)
226                 {
227                     return true;
228                 }
229             }
230         }
231 
232         return false;
233     }
234 
235     public boolean wasBusy()
236     {
237         return wasInState(ChannelState.BUSY) 
238                 || hangupCause == HangupCause.AST_CAUSE_BUSY
239                 || hangupCause == HangupCause.AST_CAUSE_USER_BUSY;
240     }
241 
242     /***
243      * Changes the state of this channel.
244      * 
245      * @param date when the state change occurred.
246      * @param state the new state of this channel.
247      */
248     synchronized void stateChanged(Date date, ChannelState state)
249     {
250         final ChannelStateHistoryEntry historyEntry;
251         final ChannelState oldState = this.state;
252 
253         if (oldState == state)
254         {
255             return;
256         }
257 
258         // System.err.println(id + " state change: " + oldState + " => " + state
259         // + " (" + name + ")");
260         historyEntry = new ChannelStateHistoryEntry(date, state);
261         synchronized (stateHistory)
262         {
263             stateHistory.add(historyEntry);
264         }
265 
266         this.state = state;
267         firePropertyChange(PROPERTY_STATE, oldState, state);
268     }
269 
270     public String getAccount()
271     {
272         return account;
273     }
274 
275     /***
276      * Sets the account code used to bill this channel.
277      * 
278      * @param account the account code used to bill this channel.
279      */
280     void setAccount(String account)
281     {
282         final String oldAccount = this.account;
283 
284         this.account = account;
285         firePropertyChange(PROPERTY_ACCOUNT, oldAccount, account);
286     }
287 
288     public Extension getCurrentExtension()
289     {
290         final Extension extension;
291 
292         synchronized (extensionHistory)
293         {
294             if (extensionHistory.isEmpty())
295             {
296                 extension = null;
297             }
298             else
299             {
300                 extension = extensionHistory.get(extensionHistory.size() - 1).getExtension();
301             }
302         }
303 
304         return extension;
305     }
306 
307     public Extension getFirstExtension()
308     {
309         final Extension extension;
310 
311         synchronized (extensionHistory)
312         {
313             if (extensionHistory.isEmpty())
314             {
315                 extension = null;
316             }
317             else
318             {
319                 extension = extensionHistory.get(0).getExtension();
320             }
321         }
322 
323         return extension;
324     }
325 
326     public List<ExtensionHistoryEntry> getExtensionHistory()
327     {
328         final List<ExtensionHistoryEntry> copy;
329 
330         synchronized (extensionHistory)
331         {
332             copy = new ArrayList<ExtensionHistoryEntry>(extensionHistory);
333         }
334 
335         return copy;
336     }
337 
338     /***
339      * Adds a visted dialplan entry to the history.
340      * 
341      * @param date the date the extension has been visited.
342      * @param extension the visted dialplan entry to add.
343      */
344     void extensionVisited(Date date, Extension extension)
345     {
346         final Extension oldCurrentExtension = getCurrentExtension();
347         final ExtensionHistoryEntry historyEntry;
348 
349         historyEntry = new ExtensionHistoryEntry(date, extension);
350 
351         synchronized (extensionHistory)
352         {
353             extensionHistory.add(historyEntry);
354         }
355 
356         firePropertyChange(PROPERTY_CURRENT_EXTENSION, oldCurrentExtension, extension);
357     }
358 
359     public Date getDateOfCreation()
360     {
361         return dateOfCreation;
362     }
363 
364     public Date getDateOfRemoval()
365     {
366         return dateOfRemoval;
367     }
368 
369     public HangupCause getHangupCause()
370     {
371         return hangupCause;
372     }
373 
374     public String getHangupCauseText()
375     {
376         return hangupCauseText;
377     }
378 
379     public CallDetailRecord getCallDetailRecord()
380     {
381         return callDetailRecord;
382     }
383 
384     void callDetailRecordReceived(Date date, CallDetailRecordImpl callDetailRecord)
385     {
386         final CallDetailRecordImpl oldCallDetailRecord = this.callDetailRecord;
387 
388         this.callDetailRecord = callDetailRecord;
389         firePropertyChange(PROPERTY_CALL_DETAIL_RECORD, oldCallDetailRecord, callDetailRecord);
390     }
391 
392     /***
393      * Sets dateOfRemoval, hangupCause and hangupCauseText and changes state to
394      * {@link ChannelState#HUNGUP}. Fires a PropertyChangeEvent for state.
395      * 
396      * @param dateOfRemoval date the channel was hung up
397      * @param hangupCause cause for hangup
398      * @param hangupCauseText textual representation of hangup cause
399      */
400     synchronized void hungup(Date dateOfRemoval, HangupCause hangupCause, String hangupCauseText)
401     {
402         this.dateOfRemoval = dateOfRemoval;
403         this.hangupCause = hangupCause;
404         this.hangupCauseText = hangupCauseText;
405         // update state and fire PropertyChangeEvent
406         stateChanged(dateOfRemoval, ChannelState.HUNGUP);
407     }
408 
409     /* dialed channels */
410 
411     public AsteriskChannel getDialedChannel()
412     {
413         return dialedChannel;
414     }
415 
416     public List<DialedChannelHistoryEntry> getDialedChannelHistory()
417     {
418         final List<DialedChannelHistoryEntry> copy;
419 
420         synchronized (linkedChannelHistory)
421         {
422             copy = new ArrayList<DialedChannelHistoryEntry>(dialedChannelHistory);
423         }
424 
425         return copy;
426     }
427 
428     synchronized void channelDialed(Date date, AsteriskChannel dialedChannel)
429     {
430         final AsteriskChannel oldDialedChannel = this.dialedChannel;
431         final DialedChannelHistoryEntry historyEntry;
432 
433         historyEntry = new DialedChannelHistoryEntry(date, dialedChannel);
434         synchronized (dialedChannelHistory)
435         {
436             dialedChannelHistory.add(historyEntry);
437         }
438         this.dialedChannel = dialedChannel;
439         firePropertyChange(PROPERTY_DIALED_CHANNEL, oldDialedChannel, dialedChannel);
440     }
441 
442     /* dialed channels */
443 
444     public AsteriskChannel getDialingChannel()
445     {
446         return dialingChannel;
447     }
448 
449     synchronized void channelDialing(Date date, AsteriskChannel dialingChannel)
450     {
451         final AsteriskChannel oldDialingChannel = this.dialingChannel;
452 
453         this.dialingChannel = dialingChannel;
454         firePropertyChange(PROPERTY_DIALING_CHANNEL, oldDialingChannel, dialingChannel);
455     }
456 
457     /* linked channels */
458 
459     public AsteriskChannel getLinkedChannel()
460     {
461         return linkedChannel;
462     }
463 
464     public List<LinkedChannelHistoryEntry> getLinkedChannelHistory()
465     {
466         final List<LinkedChannelHistoryEntry> copy;
467 
468         synchronized (linkedChannelHistory)
469         {
470             copy = new ArrayList<LinkedChannelHistoryEntry>(linkedChannelHistory);
471         }
472 
473         return copy;
474     }
475 
476     public boolean wasLinked()
477     {
478         return wasLinked;
479     }
480 
481     /***
482      * Sets the channel this channel is bridged with.
483      * 
484      * @param linkedChannel the channel this channel is bridged with.
485      */
486     synchronized void channelLinked(Date date, AsteriskChannel linkedChannel)
487     {
488         final AsteriskChannel oldLinkedChannel = this.linkedChannel;
489         final LinkedChannelHistoryEntry historyEntry;
490 
491         historyEntry = new LinkedChannelHistoryEntry(date, linkedChannel);
492         synchronized (linkedChannelHistory)
493         {
494             linkedChannelHistory.add(historyEntry);
495         }
496         this.linkedChannel = linkedChannel;
497         this.wasLinked = true;
498         firePropertyChange(PROPERTY_LINKED_CHANNEL, oldLinkedChannel, linkedChannel);
499     }
500 
501     synchronized void channelUnlinked(Date date)
502     {
503         final AsteriskChannel oldLinkedChannel = this.linkedChannel;
504         final LinkedChannelHistoryEntry historyEntry;
505 
506         synchronized (linkedChannelHistory)
507         {
508             if (linkedChannelHistory.isEmpty())
509             {
510                 historyEntry = null;
511             }
512             else
513             {
514                 historyEntry = linkedChannelHistory.get(linkedChannelHistory.size() - 1);
515             }
516         }
517 
518         if (historyEntry != null)
519         {
520             historyEntry.setDateUnlinked(date);
521         }
522         this.linkedChannel = null;
523         firePropertyChange(PROPERTY_LINKED_CHANNEL, oldLinkedChannel, null);
524     }
525 
526     /* MeetMe user */
527 
528     public MeetMeUserImpl getMeetMeUser()
529     {
530         return meetMeUserImpl;
531     }
532 
533     void setMeetMeUserImpl(MeetMeUserImpl meetMeUserImpl)
534     {
535         final MeetMeUserImpl oldMeetMeUserImpl = this.meetMeUserImpl;
536         this.meetMeUserImpl = meetMeUserImpl;
537         firePropertyChange(PROPERTY_MEET_ME_USER, oldMeetMeUserImpl, meetMeUserImpl);
538     }
539 
540     // action methods
541 
542     public void hangup() throws ManagerCommunicationException, NoSuchChannelException
543     {
544         ManagerResponse response;
545 
546         response = server.sendAction(new HangupAction(name));
547         if (response instanceof ManagerError)
548         {
549             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
550         }
551     }
552 
553     public void hangup(HangupCause cause) throws ManagerCommunicationException, NoSuchChannelException
554     {
555         if (cause != null)
556         {
557             setVariable(CAUSE_VARIABLE_NAME, Integer.toString(cause.getCode()));
558         }
559         hangup();
560     }
561 
562     public void setAbsoluteTimeout(int seconds) throws ManagerCommunicationException, NoSuchChannelException
563     {
564         ManagerResponse response;
565 
566         response = server.sendAction(new AbsoluteTimeoutAction(name, seconds));
567         if (response instanceof ManagerError)
568         {
569             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
570         }
571     }
572 
573     public void redirect(String context, String exten, int priority) throws ManagerCommunicationException,
574             NoSuchChannelException
575     {
576         ManagerResponse response;
577 
578         response = server.sendAction(new RedirectAction(name, context, exten, priority));
579         if (response instanceof ManagerError)
580         {
581             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
582         }
583     }
584 
585     public void redirectBothLegs(String context, String exten, int priority) throws ManagerCommunicationException,
586             NoSuchChannelException
587     {
588         ManagerResponse response;
589 
590         if (linkedChannel == null)
591         {
592             response = server.sendAction(new RedirectAction(name, context, exten, priority));
593         }
594         else
595         {
596             response = server.sendAction(new RedirectAction(name, linkedChannel.getName(), context, exten, priority,
597                     context, exten, priority));
598         }
599 
600         if (response instanceof ManagerError)
601         {
602             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
603         }
604     }
605 
606     public String getVariable(String variable) throws ManagerCommunicationException, NoSuchChannelException
607     {
608         ManagerResponse response;
609         String value;
610 
611         response = server.sendAction(new GetVarAction(name, variable));
612         if (response instanceof ManagerError)
613         {
614             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
615         }
616         value = response.getAttribute("Value");
617         if (value == null)
618         {
619             value = response.getAttribute(variable); // for Asterisk 1.0.x
620         }
621         return value;
622     }
623 
624     public void setVariable(String variable, String value) throws ManagerCommunicationException, NoSuchChannelException
625     {
626         ManagerResponse response;
627 
628         response = server.sendAction(new SetVarAction(name, variable, value));
629         if (response instanceof ManagerError)
630         {
631             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
632         }
633     }
634 
635     public void playDtmf(String digit) throws ManagerCommunicationException, NoSuchChannelException,
636             IllegalArgumentException
637     {
638         ManagerResponse response;
639 
640         if (digit == null)
641         {
642             throw new IllegalArgumentException("DTMF digit to send must not be null");
643         }
644 
645         response = server.sendAction(new PlayDtmfAction(name, digit));
646         if (response instanceof ManagerError)
647         {
648             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
649         }
650     }
651 
652     public void startMonitoring(String filename) throws ManagerCommunicationException, NoSuchChannelException
653     {
654         startMonitoring(filename, null, false);
655     }
656 
657     public void startMonitoring(String filename, String format) throws ManagerCommunicationException, NoSuchChannelException
658     {
659         startMonitoring(filename, format, false);
660     }
661 
662     public void startMonitoring(String filename, String format, boolean mix) throws ManagerCommunicationException,
663             NoSuchChannelException
664     {
665         ManagerResponse response;
666 
667         response = server.sendAction(new MonitorAction(name, filename, format, mix));
668         if (response instanceof ManagerError)
669         {
670             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
671         }
672     }
673 
674     public void changeMonitoring(String filename) throws ManagerCommunicationException, NoSuchChannelException,
675             IllegalArgumentException
676     {
677         ManagerResponse response;
678 
679         if (filename == null)
680         {
681             throw new IllegalArgumentException("New filename must not be null");
682         }
683 
684         response = server.sendAction(new ChangeMonitorAction(name, filename));
685         if (response instanceof ManagerError)
686         {
687             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
688         }
689     }
690 
691     public void stopMonitoring() throws ManagerCommunicationException, NoSuchChannelException
692     {
693         ManagerResponse response;
694 
695         response = server.sendAction(new StopMonitorAction(name));
696         if (response instanceof ManagerError)
697         {
698             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
699         }
700     }
701 
702     public void pauseMonitoring() throws ManagerCommunicationException, NoSuchChannelException
703     {
704         ManagerResponse response;
705 
706         response = server.sendAction(new PauseMonitorAction(name));
707         if (response instanceof ManagerError)
708         {
709             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
710         }
711     }
712 
713     public void unpauseMonitoring() throws ManagerCommunicationException, NoSuchChannelException
714     {
715         ManagerResponse response;
716 
717         response = server.sendAction(new UnpauseMonitorAction(name));
718         if (response instanceof ManagerError)
719         {
720             throw new NoSuchChannelException("Channel '" + name + "' is not available: " + response.getMessage());
721         }
722     }
723 
724     @Override
725    public String toString()
726     {
727         final StringBuffer sb;
728         final AsteriskChannel dialedChannel;
729         final AsteriskChannel dialingChannel;
730         final AsteriskChannel linkedChannel;
731 
732         sb = new StringBuffer("AsteriskChannel[");
733 
734         synchronized (this)
735         {
736             sb.append("id='" + getId() + "',");
737             sb.append("name='" + getName() + "',");
738             sb.append("callerId='" + getCallerId() + "',");
739             sb.append("state='" + getState() + "',");
740             sb.append("account='" + getAccount() + "',");
741             sb.append("dateOfCreation=" + getDateOfCreation() + ",");
742             dialedChannel = this.dialedChannel;
743             dialingChannel = this.dialingChannel;
744             linkedChannel = this.linkedChannel;
745         }
746         if (dialedChannel == null)
747         {
748             sb.append("dialedChannel=null,");
749         }
750         else
751         {
752             sb.append("dialedChannel=AsteriskChannel[");
753             synchronized (dialedChannel)
754             {
755                 sb.append("id='" + dialedChannel.getId() + "',");
756                 sb.append("name='" + dialedChannel.getName() + "'],");
757             }
758         }
759         if (dialingChannel == null)
760         {
761             sb.append("dialingChannel=null,");
762         }
763         else
764         {
765             sb.append("dialingChannel=AsteriskChannel[");
766             synchronized (dialingChannel)
767             {
768                 sb.append("id='" + dialingChannel.getId() + "',");
769                 sb.append("name='" + dialingChannel.getName() + "'],");
770             }
771         }
772         if (linkedChannel == null)
773         {
774             sb.append("linkedChannel=null");
775         }
776         else
777         {
778             sb.append("linkedChannel=AsteriskChannel[");
779             synchronized (linkedChannel)
780             {
781                 sb.append("id='" + linkedChannel.getId() + "',");
782                 sb.append("name='" + linkedChannel.getName() + "']");
783             }
784         }
785         sb.append("]");
786 
787         return sb.toString();
788     }
789 }