AsteriskRuby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,295 @@
1
+ =begin rdoc
2
+ Copyright (c) 2007, Vonage Holdings
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of Vonage Holdings nor the names of its
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ There are several ways to use the AGIServer module. All of them have a few things in common. In general, since we're creating a server, we need a way to cleanly kill it. So we setup sigint anf sigterm handlers to shutdown all instances of AGIServer Objects
33
+
34
+ trap('INT') { AGIServer.shutdown }
35
+ trap('TERM') { AGIServer.shutdown }
36
+
37
+ We also tend to use a logger because this should be daemonized. While developing, I reccomend you log to STDERR
38
+
39
+ logger = Logger.new(STDERR)
40
+ logger.level = Logger::DEBUG
41
+
42
+ I use YAML for configuration options. This just sets up the bind port, address, and some threading configuration options.
43
+
44
+ config = YAML.load_file('config/example-config.yaml')
45
+ config[:logger] = logger
46
+ config[:params] = {:custom1 => 'data1'}
47
+
48
+ And then we generate our server
49
+
50
+ begin
51
+ MyAgiServer = AGIServer.new(config)
52
+ rescue Errno::EADDRINUSE
53
+ error = "Cannot start MyAgiServer, Address already in use."
54
+ logger.fatal(error)
55
+ print "#{error}\n"
56
+ exit
57
+ else
58
+ print "#{$$}"
59
+ end
60
+
61
+ In this example, I'll show you the rails-routing means of working with the AGIServer. Define a Route class along with a few routes, and start the server.
62
+
63
+ class TestRoutes < AGIRoute
64
+ def sample
65
+ agi.answer
66
+ print "CUSTOM1 = [#{params[:custom1]}]\n"
67
+ print "URI = [#{request[:uri]}]\n"
68
+ print "ID = [#{request[:id]}]\n"
69
+ print "METHOD = [#{request[:method]}]\n"
70
+ print "OPTIONS = #{request[:options].pretty_inspect}"
71
+ print "FOO = [#{request[:options]['foo']}]\n"
72
+ print '-' * 10 + "\n"
73
+ helper_method
74
+ agi.hangup
75
+ end
76
+ private
77
+ def helper_method
78
+ print "I'm private which means I'm not accessible as a route!\n"
79
+ end
80
+ end
81
+ MyAgiServer.start
82
+ MyAgiServer.finish
83
+
84
+ Pointing an asterisk extension at agi://localhost:4573/TestRoutes/sample/1/?foo=bar will execute the sample method in the TestRoutes class.
85
+
86
+ In this example, I'll show you how to use a block to define the AGI logic. Simply start the server and pass it a block expecting an agi object:
87
+
88
+ MyAgiServer.start do |agi|
89
+ agi.answer
90
+ puts "I'm Alive!"
91
+ agi.hangup
92
+ end
93
+ MyAgiServer.finish
94
+
95
+ In this example, I'll show you another way to use a block to define the AGI logic. This block makes configuration parameters available during the call:
96
+
97
+ MyAgiServer.start do |agi,params|
98
+ agi.answer
99
+ print "PARAMS = #{params.pretty_inspect}"
100
+ agi.hangup
101
+ end
102
+ MyAgiServer.finish
103
+
104
+ =end
105
+ #AGIServer is a threaded server framework that is intended to be used to communicate with an Asterisk PBX via the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements a server object which will listen on a tcp host:port and accept connections, setup an AGI object, and either yield to a supplied block, which itself defines callflow, or route to public methods of the AGIRoute objects.
106
+ require 'socket'
107
+ require 'thread'
108
+ require 'logger'
109
+ require 'AGI.rb'
110
+ require 'AGIExceptions'
111
+ require 'AGIRouter'
112
+
113
+ #AGIServer is a threaded server framework that is intended to be used to communicate with an Asterisk PBX via the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements a server object which will listen on a tcp host:port and accept connections, setup an AGI object, and either yield to a supplied block, which itself defines callflow, or route to public methods of the AGIRoute objects.
114
+ class AGIServer
115
+ #A list of all current AGIServers
116
+ @@servers = []
117
+ #Binding Parameters supplied during initialization.
118
+ attr_reader :bind_host, :bind_port
119
+ #Creates an AGIServer Object based on the provided Parameter Hash, and binds to the appropriate host/port. Will also set signal handlers that will shut down all AGIServer's upon receipt of SIGINT or SIGTERM.
120
+ #* :bind_host sets the hostname or ip address to bind to. Defaults to localhost.
121
+ #* :bind_port sets the port to bind to. Defaults to 4573.
122
+ #* :max_workers sets the maximum number of worker threads to allow for connection processing. Defaults to 10
123
+ #* :min_workers sets the minimum number of worker threads to maintain for connection processing. Defaults to 5
124
+ #* :jobs_per_worker sets the number of connections each worker will handle before exiting. Defaults to 50
125
+ #* :logger sets the Logger object to use for logging. Defaults to Logger.new(STDERR).
126
+ #* :params can be any object you wish to be made available to all workers; I suggest a hash of objects.
127
+ def initialize(params={})
128
+ #Options
129
+ @bind_host = params[:bind_host] || 'localhost'
130
+ @bind_port = params[:bind_port] || 4573
131
+ @max_workers = params[:max_workers] || 10
132
+ @min_workers = params[:min_workers] || 5
133
+ @jobs_per_worker = params[:jobs_per_worker] || 50
134
+ @logger = params[:logger] || Logger.new(STDERR)
135
+ @stats = params[:stats] || false
136
+ @params = params[:params] || Hash.new
137
+
138
+ #Threads
139
+ @listener = nil
140
+ @monitor = nil
141
+ @workers = []
142
+
143
+ #Synchronization
144
+ @worker_queue = Queue.new
145
+ @shutdown = false
146
+
147
+ #Initial Bind
148
+ begin
149
+ @listen_socket = TCPServer.new(@bind_host, @bind_port)
150
+ rescue Errno::EADDRINUSE
151
+ @logger.fatal("AGIServer cannot bind to #{@bind_host}:#{@bind_port}, Address already in use.")
152
+ raise
153
+ end
154
+
155
+ #Track for signal handling
156
+ @@servers << self
157
+ AGIRouter.logger(@logger)
158
+
159
+ trap('INT') { shutdown }
160
+ trap('TERM') { shutdown }
161
+ end
162
+ #call-seq:
163
+ # run()
164
+ # run() { |agi| block }
165
+ # run() { |agi,params| block }
166
+ #
167
+ #Starts the server to run. If a block is provided, the block will be run by all workers to handle connections. If a block is not provided, will attempt to route calls to public methods of AGIRoute objects.
168
+ #
169
+ #1. Listener Thread: The Listener Thread is the simplest of the Threads. It accepts client sockets from the main socket, and enqueues those client sockets into the worker_queue.
170
+ #2. Worker Threads: The Worker Thread is also fairly simple. It loops jobs_per_worker times, and each time, dequeues from the worker_queue. If the result is nil, it exits, otherwise, it interacts with the client socket, either yielding to the aforementioned supplied block or routing to the AGIRoutes. If a Worker Thread is instantiated, it will continue to process requests until it processes jobs_per_worker jobs or the server is stopped.
171
+ #3. Monitor Thread: The Monitor Thread is the most complex of the threads at use. It instantiates Worker Threads if at any time it detects that there are fewer workers than min_workers, and if at any time it detects that the worker_queue length is greater than zero while there are fewer than max_workers.
172
+ def run(&block)
173
+ @logger.info{"AGIServer Initializing Monitor Thread"}
174
+ @monitor = Thread.new do
175
+ poll = 0
176
+ while ! @shutdown do
177
+ poll += 1
178
+ if (@workers.length < @max_workers and @worker_queue.length > 0) or ( @workers.length < @min_workers ) then
179
+ @logger.info{"AGIServer Starting Worker Thread to handle requests"}
180
+
181
+ #Begin Worker Thread
182
+ worker_thread = Thread.new do
183
+ @jobs_per_worker.times do
184
+ client = @worker_queue.deq
185
+ break if client.nil?
186
+ @logger.debug{"AGIServer Worker received Connection"}
187
+ agi = AGI.new({ :input => client, :output => client, :logger => @logger })
188
+ begin
189
+ agi.init
190
+ params = @params
191
+ if block.nil?
192
+ router = AGIRouter.new(agi.channel_params['request'])
193
+ router.route(agi, params)
194
+ else
195
+ if block.arity == 2
196
+ yield(agi, params)
197
+ elsif block.arity == 1
198
+ yield(agi)
199
+ end
200
+ end
201
+ rescue AGIHangupError => error
202
+ @logger.error{"AGIServer Worker Caught Unhandled Hangup: #{error}"}
203
+ rescue AGIError => error
204
+ @logger.error{"AGIServer Worker Caught Unhandled Exception: #{error.class} #{error.to_s}"}
205
+ rescue Exception => error
206
+ @logger.error{"AGIServer Worker Got Unhandled Exception: #{error.class} #{error}"}
207
+ ensure
208
+ client.close
209
+ @logger.debug{"AGIServer Worker done with Connection"}
210
+ end
211
+ end
212
+ @workers.delete(Thread.current)
213
+ @logger.info{"AGIServer Worker handled last Connection, terminating"}
214
+ end
215
+ #End Worker Thread
216
+
217
+ @workers << worker_thread
218
+ next #Short Circuit back without a sleep in case we need more threads for load
219
+ end
220
+ if @stats and poll % 10 == 0 then
221
+ @logger.debug{"AGIServer #{@workers.length} active workers, #{@worker_queue.length} jobs waiting"}
222
+ end
223
+ sleep 1
224
+ end
225
+ @logger.debug{"AGIServer Signaling all Worker Threads to finish up and exit"}
226
+ @workers.length.times{ @worker_queue.enq(nil) }
227
+ @workers.each { |worker| worker.join }
228
+ @logger.debug{"AGIServer Final Worker Thread closed"}
229
+ end
230
+
231
+ @logger.info{"AGIServer Initializing Listener Thread"}
232
+ @listener = Thread.new do
233
+ begin
234
+ while( client = @listen_socket.accept )
235
+ @logger.debug{"AGIServer Listener received Connection Request"}
236
+ @worker_queue.enq(client)
237
+ end
238
+ rescue IOError
239
+ # Occurs on socket shutdown.
240
+ end
241
+ end
242
+ end
243
+ alias_method :start, :run
244
+
245
+ #Will wait for the Monitor and Listener threads to join. The Monitor thread itself will wait for all of it's instantiated Worker threads to join.
246
+ def join
247
+ @listener.join && @logger.debug{"AGIServer Listener Thread closed"}
248
+ @monitor.join && @logger.debug{"AGIServer Monitor Thread closed"}
249
+ end
250
+ alias_method :finish, :join
251
+
252
+ #Closes the listener socket, so that no new requests will be accepted. Signals to the Monitor thread to shutdown it's Workers when they're done with their current clients.
253
+ def shutdown
254
+ @logger.info{"AGIServer Shutting down gracefully"}
255
+ @listen_socket.close && @logger.info{"AGIServer No longer accepting connections"}
256
+ @shutdown = true && @logger.info{"AGIServer Signaling Monitor to close after active sessions complete"}
257
+ end
258
+ alias_method :stop, :shutdown
259
+
260
+ #Calls shutdown on all AGIServer objects.
261
+ def AGIServer.shutdown
262
+ @@servers.each { |server| server.shutdown }
263
+ end
264
+
265
+ end
266
+
267
+ =begin
268
+ Copyright (c) 2007, Vonage Holdings
269
+
270
+ All rights reserved.
271
+
272
+ Redistribution and use in source and binary forms, with or without
273
+ modification, are permitted provided that the following conditions are met:
274
+
275
+ * Redistributions of source code must retain the above copyright
276
+ notice, this list of conditions and the following disclaimer.
277
+ * Redistributions in binary form must reproduce the above copyright
278
+ notice, this list of conditions and the following disclaimer in the
279
+ documentation and/or other materials provided with the distribution.
280
+ * Neither the name of Vonage Holdings nor the names of its
281
+ contributors may be used to endorse or promote products derived from this
282
+ software without specific prior written permission.
283
+
284
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
285
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
286
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
287
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
288
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
289
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
290
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
291
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
292
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
293
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
294
+ POSSIBILITY OF SUCH DAMAGE.
295
+ =end
@@ -0,0 +1,91 @@
1
+ =begin rdoc
2
+ Copyright (c) 2007, Vonage Holdings
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of Vonage Holdings nor the names of its
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ AGIState is meant to be subclassed to implement any state that needs to persist throughout an AGI session within the framework. By default, it can be sed to increase and reset failiure counters, and have an AGIStateException thrown when conditions are met.
33
+ =end
34
+
35
+ require 'AGIExceptions'
36
+ #AGIState is meant to be subclassed to implement any state that needs to persist throughout an AGI session within the framework. By default, it can be sed to increase and reset failiure counters, and have an AGIStateException thrown when conditions are met.
37
+ class AGIState
38
+ @@failure_threshold = 3
39
+ attr_reader :failures
40
+ attr_reader :failure_threshold
41
+ def initialize(conf={})
42
+ @failures = 0
43
+ @failure_threshold = conf[:threshold] || @@failure_threshold
44
+ @failure_threshold = conf[:failure_threshold] || @failure_threshold
45
+ end
46
+ def self.failure_threshold=(threshold)
47
+ @@failure_threshold = threshold
48
+ end
49
+ def failure_inc
50
+ @failures += 1
51
+ if @failures >= @failure_threshold then
52
+ raise AGIStateFailure.new("Too many failures ( #{@failures} >= #{@failure_threshold})" )
53
+ end
54
+ end
55
+ def failure_reset
56
+ @failures = 0
57
+ if @failures >= @failure_threshold then
58
+ raise AGIStateFailure.new("Too many failures ( #{@failures} >= #{@failure_threshold})")
59
+ end
60
+ end
61
+ end
62
+
63
+ =begin
64
+ Copyright (c) 2007, Vonage Holdings
65
+
66
+ All rights reserved.
67
+
68
+ Redistribution and use in source and binary forms, with or without
69
+ modification, are permitted provided that the following conditions are met:
70
+
71
+ * Redistributions of source code must retain the above copyright
72
+ notice, this list of conditions and the following disclaimer.
73
+ * Redistributions in binary form must reproduce the above copyright
74
+ notice, this list of conditions and the following disclaimer in the
75
+ documentation and/or other materials provided with the distribution.
76
+ * Neither the name of Vonage Holdings nor the names of its
77
+ contributors may be used to endorse or promote products derived from this
78
+ software without specific prior written permission.
79
+
80
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
81
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
82
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
83
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
84
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
85
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
86
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
87
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
88
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
89
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
90
+ POSSIBILITY OF SUCH DAMAGE.
91
+ =end
@@ -0,0 +1,65 @@
1
+ =begin rdoc
2
+ Copyright (c) 2007, Vonage Holdings
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of Vonage Holdings nor the names of its
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ This is a meta package for the framework at large.
33
+ =end
34
+
35
+ require 'AGIFramework'
36
+
37
+ =begin
38
+ Copyright (c) 2007, Vonage Holdings
39
+
40
+ All rights reserved.
41
+
42
+ Redistribution and use in source and binary forms, with or without
43
+ modification, are permitted provided that the following conditions are met:
44
+
45
+ * Redistributions of source code must retain the above copyright
46
+ notice, this list of conditions and the following disclaimer.
47
+ * Redistributions in binary form must reproduce the above copyright
48
+ notice, this list of conditions and the following disclaimer in the
49
+ documentation and/or other materials provided with the distribution.
50
+ * Neither the name of Vonage Holdings nor the names of its
51
+ contributors may be used to endorse or promote products derived from this
52
+ software without specific prior written permission.
53
+
54
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
58
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
59
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
60
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
61
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
62
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
63
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
64
+ POSSIBILITY OF SUCH DAMAGE.
65
+ =end