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.manager.internal;
18  
19  import org.asteriskjava.manager.event.DisconnectEvent;
20  import org.asteriskjava.manager.event.ManagerEvent;
21  import org.asteriskjava.manager.event.ProtocolIdentifierReceivedEvent;
22  import org.asteriskjava.manager.response.CommandResponse;
23  import org.asteriskjava.manager.response.ManagerResponse;
24  import org.asteriskjava.util.DateUtil;
25  import org.asteriskjava.util.Log;
26  import org.asteriskjava.util.LogFactory;
27  import org.asteriskjava.util.SocketConnectionFacade;
28  
29  import java.io.IOException;
30  import java.util.*;
31  
32  
33  /***
34   * Default implementation of the ManagerReader interface.
35   *
36   * @author srt
37   * @version $Id: ManagerReaderImpl.java 878 2007-08-01 22:04:11Z srt $
38   */
39  public class ManagerReaderImpl implements ManagerReader
40  {
41      /***
42       * Instance logger.
43       */
44      private final Log logger = LogFactory.getLog(getClass());
45  
46      /***
47       * The event builder utility to convert a map of attributes reveived from asterisk to instances
48       * of registered event classes.
49       */
50      private final EventBuilder eventBuilder;
51  
52      /***
53       * The response builder utility to convert a map of attributes reveived from asterisk to
54       * instances of well known response classes.
55       */
56      private final ResponseBuilder responseBuilder;
57  
58      /***
59       * The dispatcher to use for dispatching events and responses.
60       */
61      private final Dispatcher dispatcher;
62  
63      /***
64       * The source to use when creating {@link ManagerEvent}s.
65       */
66      private final Object source;
67  
68      /***
69       * The socket to use for reading from the asterisk server.
70       */
71      private SocketConnectionFacade socket;
72  
73      /***
74       * If set to <code>true</code>, terminates and closes the reader.
75       */
76      private boolean die = false;
77  
78      /***
79       * <code>true</code> if the main loop has finished.
80       */
81      private boolean dead = false;
82  
83      /***
84       * Exception that caused this reader to terminate if any.
85       */
86      private IOException terminationException;
87  
88      /***
89       * Creates a new ManagerReaderImpl.
90       *
91       * @param dispatcher the dispatcher to use for dispatching events and responses.
92       * @param source     the source to use when creating {@link ManagerEvent}s
93       */
94      public ManagerReaderImpl(final Dispatcher dispatcher, Object source)
95      {
96          this.dispatcher = dispatcher;
97          this.source = source;
98  
99          this.eventBuilder = new EventBuilderImpl();
100         this.responseBuilder = new ResponseBuilderImpl();
101     }
102 
103     /***
104      * Sets the socket to use for reading from the asterisk server.
105      *
106      * @param socket the socket to use for reading from the asterisk server.
107      */
108     public void setSocket(final SocketConnectionFacade socket)
109     {
110         this.socket = socket;
111     }
112 
113     public void registerEventClass(Class eventClass)
114     {
115         eventBuilder.registerEventClass(eventClass);
116     }
117 
118     /***
119      * Reads line by line from the asterisk server, sets the protocol identifier (using a
120      * generated {@link org.asteriskjava.manager.event.ProtocolIdentifierReceivedEvent}) as soon as it is
121      * received and dispatches the received events and responses via the associated dispatcher.
122      *
123      * @see org.asteriskjava.manager.internal.Dispatcher#dispatchEvent(ManagerEvent)
124      * @see org.asteriskjava.manager.internal.Dispatcher#dispatchResponse(ManagerResponse)
125      */
126     public void run()
127     {
128         final Map<String, String> buffer = new HashMap<String, String>();
129         final List<String> commandResult = new ArrayList<String>();
130         String line;
131         boolean processingCommandResult = false;
132 
133         if (socket == null)
134         {
135             throw new IllegalStateException("Unable to run: socket is null.");
136         }
137 
138         this.die = false;
139         this.dead = false;
140 
141         try
142         {
143             // main loop
144             while ((line = socket.readLine()) != null && !this.die)
145             {
146                 // dirty hack for handling the CommandAction. Needs fix when manager protocol is
147                 // enhanced.
148                 if (processingCommandResult)
149                 {
150                     // in case of an error Asterisk sends a Usage: and an END COMMAND
151                     // that is prepended by a space :(
152                     if ("--END COMMAND--".equals(line) || " --END COMMAND--".equals(line))
153                     {
154                         CommandResponse commandResponse = new CommandResponse();
155 
156                         for (int crIdx = 0; crIdx < commandResult.size(); crIdx++)
157                         {
158                             String[] crNVPair = commandResult.get(crIdx).split(" *: *", 2);
159 
160                             if (crNVPair[0].equalsIgnoreCase("ActionID"))
161                             {
162                                 // Remove the command response nvpair from the 
163                                 // command result array and decrement index so we
164                                 // don't skip the "new" current line
165                                 commandResult.remove(crIdx--);
166 
167                                 // Register the action id with the command result
168                                 commandResponse.setActionId(crNVPair[1]);
169                             }
170                             else if (crNVPair[0].equalsIgnoreCase("Privilege"))
171                             {
172                                 // Remove the command response nvpair from the 
173                                 // command result array and decrement index so we
174                                 // don't skip the "new" current line
175                                 commandResult.remove(crIdx--);
176                             }
177                             else
178                             {
179                                 // Didn't find a name:value pattern, so we're now in the 
180                                 // command results.  Stop processing the nv pairs.
181                                 break;
182                             }
183                         }
184                         commandResponse.setResponse("Follows");
185                         commandResponse.setDateReceived(DateUtil.getDate());
186                         // clone commandResult as it is reused
187                         commandResponse.setResult(new ArrayList<String>(commandResult));
188                         Map<String, String> attributes = new HashMap<String, String>();
189                         attributes.put("actionid", commandResponse.getActionId());
190                         attributes.put("response", commandResponse.getResponse());
191                         commandResponse.setAttributes(attributes);
192                         dispatcher.dispatchResponse(commandResponse);
193                         processingCommandResult = false;
194                     }
195                     else
196                     {
197                         commandResult.add(line);
198                     }
199                     continue;
200                 }
201 
202                 // Reponse: Follows indicates that the output starting on the next line until
203                 // --END COMMAND-- must be treated as raw output of a command executed by a
204                 // CommandAction.
205                 if ("Response: Follows".equalsIgnoreCase(line))
206                 {
207                     processingCommandResult = true;
208                     commandResult.clear();
209                     continue;
210                 }
211 
212                 // maybe we will find a better way to identify the protocol identifier but for now
213                 // this works quite well.
214                 if (line.startsWith("Asterisk Call Manager/") ||
215                         line.startsWith("Asterisk Call Manager Proxy/") ||
216                         line.startsWith("OpenPBX Call Manager/") ||
217                         line.startsWith("CallWeaver Call Manager/"))
218                 {
219                     ProtocolIdentifierReceivedEvent protocolIdentifierReceivedEvent;
220                     protocolIdentifierReceivedEvent = new ProtocolIdentifierReceivedEvent(source);
221                     protocolIdentifierReceivedEvent.setProtocolIdentifier(line);
222                     protocolIdentifierReceivedEvent.setDateReceived(DateUtil.getDate());
223                     dispatcher.dispatchEvent(protocolIdentifierReceivedEvent);
224                     continue;
225                 }
226 
227                 // an empty line indicates a normal response's or event's end so we build
228                 // the corresponding value object and dispatch it through the ManagerConnection.
229                 if (line.length() == 0)
230                 {
231                     if (buffer.containsKey("event"))
232                     {
233                         // TODO tracing
234                         //logger.debug("attempting to build event: " + buffer.get("event"));
235                         ManagerEvent event = buildEvent(source, buffer);
236                         if (event != null)
237                         {
238                             dispatcher.dispatchEvent(event);
239                         }
240                         else
241                         {
242                             logger.debug("buildEvent returned null");
243                         }
244                     }
245                     else if (buffer.containsKey("response"))
246                     {
247                         ManagerResponse response = buildResponse(buffer);
248                         // TODO tracing
249                         //logger.debug("attempting to build response");
250                         if (response != null)
251                         {
252                             dispatcher.dispatchResponse(response);
253                         }
254                     }
255                     else
256                     {
257                         if (buffer.size() > 0)
258                         {
259                             logger.debug("buffer contains neither response nor event");
260                         }
261                     }
262 
263                     buffer.clear();
264                 }
265                 else
266                 {
267                     int delimiterIndex;
268 
269                     delimiterIndex = line.indexOf(":");
270                     if (delimiterIndex > 0 && line.length() > delimiterIndex + 2)
271                     {
272                         String name;
273                         String value;
274 
275                         name = line.substring(0, delimiterIndex).toLowerCase(Locale.ENGLISH);
276                         value = line.substring(delimiterIndex + 2);
277 
278                         buffer.put(name, value);
279                         // TODO tracing
280                         //logger.debug("Got name [" + name + "], value: [" + value + "]");
281                     }
282                 }
283             }
284             this.dead = true;
285             logger.debug("Reached end of stream, terminating reader.");
286         }
287         catch (IOException e)
288         {
289             this.terminationException = e;
290             this.dead = true;
291             logger.info("Terminating reader thread: " + e.getMessage());
292         }
293         finally
294         {
295             this.dead = true;
296             // cleans resources and reconnects if needed
297             DisconnectEvent disconnectEvent = new DisconnectEvent(source);
298             disconnectEvent.setDateReceived(DateUtil.getDate());
299             dispatcher.dispatchEvent(disconnectEvent);
300         }
301     }
302 
303     public void die()
304     {
305         this.die = true;
306     }
307 
308     public boolean isDead()
309     {
310         return dead;
311     }
312 
313     public IOException getTerminationException()
314     {
315         return terminationException;
316     }
317 
318     private ManagerResponse buildResponse(Map<String, String> buffer)
319     {
320         ManagerResponse response;
321 
322         response = responseBuilder.buildResponse(buffer);
323 
324         if (response != null)
325         {
326             response.setDateReceived(DateUtil.getDate());
327         }
328 
329         return response;
330     }
331 
332     private ManagerEvent buildEvent(Object source, Map<String, String> buffer)
333     {
334         ManagerEvent event;
335 
336         event = eventBuilder.buildEvent(source, buffer);
337 
338         if (event != null)
339         {
340             event.setDateReceived(DateUtil.getDate());
341         }
342 
343         return event;
344     }
345 }