View Javadoc

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