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.fastagi.reply.impl;
18  
19  import java.io.*;
20  import java.util.*;
21  import java.util.regex.*;
22  
23  import net.sf.asterisk.fastagi.reply.AGIReply;
24  
25  /***
26   * Default implementation of the AGIReply interface.
27   * 
28   * @author srt
29   * @version $Id: AGIReplyImpl.java,v 1.2 2005/05/03 21:07:52 srt Exp $
30   */
31  public class AGIReplyImpl implements Serializable, AGIReply
32  {
33      private static final Pattern STATUS_PATTERN = Pattern
34              .compile("^(//d{3})[ -]");
35      private static final Pattern RESULT_PATTERN = Pattern
36              .compile("^200 result= *(//S+)");
37      private static final Pattern PARENTHESIS_PATTERN = Pattern
38              .compile("^200 result=//S* +//((.*)//)");
39      private static final Pattern ADDITIONAL_ATTRIBUTES_PATTERN = Pattern
40              .compile("^200 result=//S* +(//(.*//) )?(.+)$");
41      private static final Pattern ADDITIONAL_ATTRIBUTE_PATTERN = Pattern
42              .compile("(//S+)=(//S+)");
43      private static final Pattern SYNOPSIS_PATTERN = Pattern
44              .compile("^//s*Usage://s*(.*)//s*$");
45      private static final String END_OF_PROPER_USAGE = "520 End of proper usage.";
46  
47      private Matcher matcher;
48  
49      /***
50       * Serial version identifier.
51       */
52      private static final long serialVersionUID = 3256727294671337012L;
53  
54      private List lines;
55      private String firstLine;
56  
57      /***
58       * The result, that is the part directly following the "result=" string.
59       */
60      private String result;
61  
62      /***
63       * The status code.
64       */
65      private int status;
66  
67      /***
68       * Additional attributes contained in this reply, for example endpos.
69       */
70      private Map attributes;
71  
72      /***
73       * The contents of the parenthesis.
74       */
75      private String extra;
76  
77      /***
78       * In case of status == 520 (invalid command syntax) this attribute contains
79       * the synopsis of the command.
80       */
81      private String synopsis;
82  
83      /***
84       * In case of status == 520 (invalid command syntax) this attribute contains
85       * the usage of the command.
86       */
87      private String usage;
88  
89      public AGIReplyImpl()
90      {
91  
92      }
93  
94      public AGIReplyImpl(List lines)
95      {
96          this.lines = lines;
97          try
98          {
99              firstLine = (String) lines.get(0);
100         }
101         catch (Exception e)
102         {
103         }
104     }
105 
106     public String getFirstLine()
107     {
108         return firstLine;
109     }
110 
111     public List getLines()
112     {
113         return lines;
114     }
115 
116     /***
117      * Returns the return code (the result as int).
118      * 
119      * @return the return code or -1 if the result is not an int.
120      */
121     public int getResultCode()
122     {
123         String result;
124 
125         result = getResult();
126         if (result == null)
127         {
128             return -1;
129         }
130 
131         try
132         {
133             return Integer.parseInt(result);
134         }
135         catch (NumberFormatException e)
136         {
137             return -1;
138         }
139     }
140 
141     /***
142      * Returns the return code as character.
143      * 
144      * @return the return code as character.
145      */
146     public char getResultCodeAsChar()
147     {
148         int resultCode;
149 
150         resultCode = getResultCode();
151         if (resultCode < 0)
152         {
153             return 0x0;
154         }
155 
156         return (char) resultCode;
157     }
158 
159     private boolean resultCreated;
160 
161     /***
162      * Returns the result, that is the part directly following the "result="
163      * string.
164      * 
165      * @return the result.
166      */
167     public String getResult()
168     {
169         if (resultCreated)
170         {
171             return result;
172         }
173 
174         matcher = RESULT_PATTERN.matcher(firstLine);
175         if (matcher.find())
176         {
177             result = matcher.group(1);
178         }
179         resultCreated = true;
180         return result;
181     }
182 
183     private boolean statusCreated;
184 
185     /***
186      * Returns the status code.<br>
187      * Supported status codes are:
188      * <ul>
189      * <li>200 Success
190      * <li>510 Invalid or unknown command
191      * <li>520 Invalid command syntax
192      * </ul>
193      * 
194      * @return the status code.
195      */
196     public int getStatus()
197     {
198         if (statusCreated)
199         {
200             return status;
201         }
202 
203         matcher = STATUS_PATTERN.matcher(firstLine);
204         if (matcher.find())
205         {
206             status = Integer.parseInt(matcher.group(1));
207         }
208         statusCreated = true;
209         return status;
210     }
211 
212     private boolean attributesCreated;
213 
214     /***
215      * Returns an additional attribute contained in the reply.<br>
216      * For example the reply to the StreamFileCommand contains an additional
217      * endpos attribute indicating the frame where the playback was stopped.
218      * This can be retrieved by calling getAttribute("endpos") on the
219      * corresponding reply.
220      * 
221      * @param name the name of the attribute to retrieve. The name is case
222      *            insensitive.
223      * @return the value of the attribute or <code>null</code> if it is not
224      *         set.
225      */
226     public String getAttribute(String name)
227     {
228         if (getStatus() != SC_SUCCESS)
229         {
230             return null;
231         }
232 
233         if ("result".equalsIgnoreCase(name))
234         {
235             return getResult();
236         }
237 
238         if (!attributesCreated)
239         {
240             matcher = ADDITIONAL_ATTRIBUTES_PATTERN.matcher(firstLine);
241             if (matcher.find())
242             {
243                 String s;
244                 Matcher attributeMatcher;
245 
246                 attributes = new HashMap();
247                 s = matcher.group(2);
248                 attributeMatcher = ADDITIONAL_ATTRIBUTE_PATTERN.matcher(s);
249                 while (attributeMatcher.find())
250                 {
251                     String key;
252                     String value;
253 
254                     key = attributeMatcher.group(1);
255                     value = attributeMatcher.group(2);
256                     attributes.put(key.toLowerCase(), value);
257                 }
258             }
259             attributesCreated = true;
260         }
261 
262         if (attributes == null || attributes.isEmpty())
263         {
264             return null;
265         }
266 
267         return (String) attributes.get(name.toLowerCase());
268     }
269 
270     private boolean extraCreated;
271 
272     /***
273      * Returns the text in parenthesis contained in this reply.<br>
274      * The meaning of this property depends on the command sent. Sometimes it
275      * contains a flag like "timeout" or "hangup" or - in case of the
276      * GetVariableCommand - the value of the variable.
277      * 
278      * @return the text in the parenthesis or <code>null</code> if not set.
279      */
280     public String getExtra()
281     {
282         if (getStatus() != SC_SUCCESS)
283         {
284             return null;
285         }
286 
287         if (extraCreated)
288         {
289             return extra;
290         }
291 
292         matcher = PARENTHESIS_PATTERN.matcher(firstLine);
293         if (matcher.find())
294         {
295             extra = matcher.group(1);
296         }
297         extraCreated = true;
298         return extra;
299     }
300 
301     private boolean synopsisCreated;
302 
303     /***
304      * Returns the synopsis of the command sent if Asterisk expected a different
305      * syntax (getStatus() == SC_INVALID_COMMAND_SYNTAX).
306      * 
307      * @return the synopsis of the command sent, <code>null</code> if there
308      *         were no syntax errors.
309      */
310     public String getSynopsis()
311     {
312         if (getStatus() != SC_INVALID_COMMAND_SYNTAX)
313         {
314             return null;
315         }
316 
317         if (!synopsisCreated)
318         {
319             StringBuffer usageSB;
320 
321             if (lines.size() > 1)
322             {
323                 String secondLine;
324                 Matcher synopsisMatcher;
325 
326                 secondLine = (String) lines.get(1);
327                 synopsisMatcher = SYNOPSIS_PATTERN.matcher(secondLine);
328                 if (synopsisMatcher.find())
329                 {
330                     synopsis = synopsisMatcher.group(1);
331                 }
332             }
333             synopsisCreated = true;
334 
335             usageSB = new StringBuffer();
336             for (int i = 2; i < lines.size(); i++)
337             {
338                 String line;
339 
340                 line = (String) lines.get(i);
341                 if (END_OF_PROPER_USAGE.equals(line))
342                 {
343                     break;
344                 }
345 
346                 usageSB.append(line.trim());
347                 usageSB.append(" ");
348             }
349             usage = usageSB.toString().trim();
350         }
351         return synopsis;
352     }
353 
354     /***
355      * Returns the usage of the command sent if Asterisk expected a different
356      * syntax (getStatus() == SC_INVALID_COMMAND_SYNTAX).
357      * 
358      * @return the usage of the command sent, <code>null</code> if there were
359      *         no syntax errors.
360      */
361     public String getUsage()
362     {
363         return usage;
364     }
365 
366     public String toString()
367     {
368         StringBuffer sb;
369 
370         sb = new StringBuffer(getClass().getName() + ": ");
371         sb.append("status='" + getStatus() + "'; ");
372         if (status == SC_SUCCESS)
373         {
374             sb.append("result='" + getResult() + "'; ");
375             sb.append("extra='" + getExtra() + "'; ");
376             sb.append("attributes=" + attributes + "; ");
377         }
378         if (status == SC_INVALID_COMMAND_SYNTAX)
379         {
380             sb.append("synopsis='" + getSynopsis() + "'; ");
381         }
382         sb.append("systemHashcode=" + System.identityHashCode(this));
383 
384         return sb.toString();
385     }
386 }