norikra 1.1.2-java → 1.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90c6be383b83590b832fb6271a3642a42295ae6f
4
- data.tar.gz: 23555f2d09692459ef6fa17b85a47af61129e769
3
+ metadata.gz: e7e969ece3deb5788c7e7e76c9d6cc4961b4307c
4
+ data.tar.gz: bbfdc5a4056935c355082877e6ef347b7ded54ec
5
5
  SHA512:
6
- metadata.gz: b93442c3b3e202a880fa75f90065770b1b44b210789684b16889b24b7c10f91144488e6f2564b25e78ae0850c2a8f24c711e83c0c8b8d8a90608ee5c2e0b249e
7
- data.tar.gz: b34e828cd14c7c4fb00a2d83c17131590be520e15559c9caef5e429d46a92b311e027a12a283721de81000efbb12d79b752df3e1559a4890bef4cabf93788b2d
6
+ metadata.gz: 4ea0f4f04ba875e2432db51a179d75e3ae18e3a05fc03e945b14fdce187b61da969cd334e11d67aaf8a49b0a032b389f2f57a6c385c98f284b4ecd5bd3156156
7
+ data.tar.gz: 0add6a6f2539d188f2de82856f1d8cc222f9e6dd60b31b96acbc10167f047d9395c71273dde65a97d23b9b0838d30e0c7661b899d226077def93301e272515da
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- jruby-1.7.16.1
1
+ jruby-1.7.18
data/Changes.md CHANGED
@@ -3,6 +3,10 @@
3
3
  Changes of norikra.
4
4
 
5
5
  ## v1
6
+ * v1.2.0
7
+ * Add `NULLABLE(...)` field to query NULL explicitly
8
+ * Pluggable listener and user defined listener gems
9
+ * Fix bug to ignore fields in Esper built-in function
6
10
  * v1.1.2
7
11
  * Enable `-javaagent` option
8
12
  * Fix default/pre-set threads
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in norikra.gemspec
4
4
  gemspec
5
+
6
+ # to check plugin behaviors
7
+ gem "norikra-udf-mock"
8
+ gem "norikra-listener-mock"
data/README.md CHANGED
@@ -38,12 +38,6 @@ Fix code and tests:
38
38
  1. fix code in `lib`
39
39
  1. run `bundle exec rake`
40
40
 
41
- Run tests faster than 2 or more times:
42
-
43
- 1. execute `spork`
44
- 1. execute `script/spec_server_pry` in another terminal
45
- 1. run `rspec` in pry console (executed fastly after second times)
46
-
47
41
  Execute norikra server with target/query continuation:
48
42
 
49
43
  1. `bundle exec rake devserver`
@@ -70,6 +64,9 @@ See: http://norikra.github.io/
70
64
 
71
65
  ## Changes
72
66
 
67
+ * v1.2
68
+ * `NULLABLE()` fields
69
+ * Pluggable listeners
73
70
  * v1.1
74
71
  * Suspend/Resume queries
75
72
  * v1.0
data/lib/norikra/cli.rb CHANGED
@@ -11,70 +11,70 @@ module Norikra
11
11
  def self.register_common_start_options(klass)
12
12
  klass.module_exec {
13
13
  ### Server options
14
- option :host, :type => :string, :default => nil, :aliases => "-H", :desc => 'host address that server listen [0.0.0.0]'
15
- option :port, :type => :numeric, :default => nil, :aliases => "-P", :desc => 'port that server uses [26571]'
16
- option :'ui-port', :type => :numeric, :default => nil, :desc => 'port that Web UI server uses [26578]'
14
+ option :host, type: :string, default: nil, aliases: "-H", desc: 'host address that server listen [0.0.0.0]'
15
+ option :port, type: :numeric, default: nil, aliases: "-P", desc: 'port that server uses [26571]'
16
+ option :'ui-port', type: :numeric, default: nil, desc: 'port that Web UI server uses [26578]'
17
17
 
18
- option :stats, :type => :string, :default => nil, :aliases => "-s", \
19
- :desc => 'status file path to load/dump targets and queries [none]'
20
- option :'stats-secondary', :type => :string, :default => nil, \
21
- :desc => 'status file secondary path, to dump stats with date/time, like "stats.%Y%m%d.json" [none]'
22
- option :'suppress-dump-stat', :type => :boolean, :default => false, \
23
- :desc => 'specify not to update stat file with updated targets/queries on runtime [false]'
24
- option :'dump-stat-interval', :type => :numeric, :default => nil, \
25
- :desc => 'interval(seconds) of status file dumps on runtime [none (on shutdown only)]'
18
+ option :stats, type: :string, default: nil, aliases: "-s", \
19
+ desc: 'status file path to load/dump targets and queries [none]'
20
+ option :'stats-secondary', type: :string, default: nil, \
21
+ desc: 'status file secondary path, to dump stats with date/time, like "stats.%Y%m%d.json" [none]'
22
+ option :'suppress-dump-stat', type: :boolean, default: false, \
23
+ desc: 'specify not to update stat file with updated targets/queries on runtime [false]'
24
+ option :'dump-stat-interval', type: :numeric, default: nil, \
25
+ desc: 'interval(seconds) of status file dumps on runtime [none (on shutdown only)]'
26
26
 
27
27
  ### Daemonize options
28
- option :daemonize, :type => :boolean, :default => false, :aliases => "-d", \
29
- :desc => 'daemonize Norikra server [false (foreground)]'
30
- option :pidfile, :type => :string, :default => DEFAULT_PID_PATH, :aliases => "-p", \
31
- :desc => "pidfile path when daemonized [#{DEFAULT_PID_PATH}]"
32
- option :outfile, :type => :string, :default => nil, \
33
- :desc => "stdout redirect file when daemonized [${logdir}/norikra.out]"
28
+ option :daemonize, type: :boolean, default: false, aliases: "-d", \
29
+ desc: 'daemonize Norikra server [false (foreground)]'
30
+ option :pidfile, type: :string, default: DEFAULT_PID_PATH, aliases: "-p", \
31
+ desc: "pidfile path when daemonized [#{DEFAULT_PID_PATH}]"
32
+ option :outfile, type: :string, default: nil, \
33
+ desc: "stdout redirect file when daemonized [${logdir}/norikra.out]"
34
34
 
35
35
  ### JVM options
36
- option :'bare-jvm', :type => :boolean, :default => false, :desc => "use JVM without any recommended options"
37
- option :'gc-log', :type => :string, :default => nil, :desc => "output gc logs on specified file path"
36
+ option :'bare-jvm', type: :boolean, default: false, desc: "use JVM without any recommended options"
37
+ option :'gc-log', type: :string, default: nil, desc: "output gc logs on specified file path"
38
38
 
39
39
  ### Performance options
40
40
  # performance predefined configuration sets
41
- option :micro, :type => :boolean, :default => false, \
42
- :desc => 'development or testing (inbound:0, outbound:0, route:0, timer:0, rpc:2)'
43
- option :small, :type => :boolean, :default => false, \
44
- :desc => 'virtual or small scale servers (inbound:1, outbount:1, route:1, timer:1, rpc:2)'
45
- option :middle, :type => :boolean, :default => false, \
46
- :desc => 'rackmount servers (inbound:4, outbound:2, route:2, timer:2, rpc:4)'
47
- option :large, :type => :boolean, :default => false, \
48
- :desc => 'high performance servers (inbound: 6, outbound: 6, route:4, timer:4, rpc: 8)'
41
+ option :micro, type: :boolean, default: false, \
42
+ desc: 'development or testing (inbound:0, outbound:0, route:0, timer:0, rpc:2)'
43
+ option :small, type: :boolean, default: false, \
44
+ desc: 'virtual or small scale servers (inbound:1, outbount:1, route:1, timer:1, rpc:2)'
45
+ option :middle, type: :boolean, default: false, \
46
+ desc: 'rackmount servers (inbound:4, outbound:2, route:2, timer:2, rpc:4)'
47
+ option :large, type: :boolean, default: false, \
48
+ desc: 'high performance servers (inbound: 6, outbound: 6, route:4, timer:4, rpc: 8)'
49
49
  # Esper
50
- option :'inbound-threads', :type => :numeric, :default => nil, :desc => 'number of threads for inbound data'
51
- option :'outbound-threads', :type => :numeric, :default => nil, :desc => 'number of threads for outbound data'
52
- option :'route-threads', :type => :numeric, :default => nil, :desc => 'number of threads for events routing for query execution'
53
- option :'timer-threads', :type => :numeric, :default => nil, :desc => 'number of threads for internal timers for query execution'
50
+ option :'inbound-threads', type: :numeric, default: nil, desc: 'number of threads for inbound data'
51
+ option :'outbound-threads', type: :numeric, default: nil, desc: 'number of threads for outbound data'
52
+ option :'route-threads', type: :numeric, default: nil, desc: 'number of threads for events routing for query execution'
53
+ option :'timer-threads', type: :numeric, default: nil, desc: 'number of threads for internal timers for query execution'
54
54
  ### about capacity options of esper's capacity-bound queue processing, see Esper's thread reference.
55
55
  # http://esper.codehaus.org/esper-4.10.0/doc/reference/en-US/html/configuration.html#config-engine-threading-advanced
56
56
  # default nil: unbound queueing
57
- option :'inbound-thread-capacity', :type => :numeric, :default => nil
58
- option :'outbound-thread-capacity', :type => :numeric, :default => nil
59
- option :'route-thread-capacity', :type => :numeric, :default => nil
60
- option :'timer-thread-capacity', :type => :numeric, :default => nil
57
+ option :'inbound-thread-capacity', type: :numeric, default: nil
58
+ option :'outbound-thread-capacity', type: :numeric, default: nil
59
+ option :'route-thread-capacity', type: :numeric, default: nil
60
+ option :'timer-thread-capacity', type: :numeric, default: nil
61
61
  # Jetty
62
- option :'rpc-threads', :type => :numeric, :default => nil, :desc => 'number of threads for rpc handlers'
63
- option :'web-threads', :type => :numeric, :default => nil, :desc => 'number of threads for WebUI handlers'
62
+ option :'rpc-threads', type: :numeric, default: nil, desc: 'number of threads for rpc handlers'
63
+ option :'web-threads', type: :numeric, default: nil, desc: 'number of threads for WebUI handlers'
64
64
 
65
65
  ### Logging options
66
- option :logdir, :type => :string, :default => nil, :aliases => "-l", \
67
- :desc => "directory path of logfiles when daemonized [nil (console for foreground)]"
68
- option :'log-filesize', :type => :string, :default => nil, :desc => 'log rotation size [10MB]'
69
- option :'log-backups' , :type => :numeric, :default => nil, :desc => 'log rotation backups [10]'
70
- option :'log-buffer-lines', :type => :numeric, :default => nil, :desc => 'log lines to fetch from API [1000]'
71
- option :'log4j-properties-path', :type => :string, :default => nil, :desc => 'path to log4j.properties. ignore other log* options when this option is present'
66
+ option :logdir, type: :string, default: nil, aliases: "-l", \
67
+ desc: "directory path of logfiles when daemonized [nil (console for foreground)]"
68
+ option :'log-filesize', type: :string, default: nil, desc: 'log rotation size [10MB]'
69
+ option :'log-backups' , type: :numeric, default: nil, desc: 'log rotation backups [10]'
70
+ option :'log-buffer-lines', type: :numeric, default: nil, desc: 'log lines to fetch from API [1000]'
71
+ option :'log4j-properties-path', type: :string, default: nil, desc: 'path to log4j.properties. ignore other log* options when this option is present'
72
72
 
73
73
  ### Loglevel options
74
- option :'more-quiet', :type => :boolean, :default => false, :desc => 'set loglevel as ERROR'
75
- option :quiet, :type => :boolean, :default => false, :aliases => "-q", :desc => 'set loglevel as WARN'
76
- option :verbose, :type => :boolean, :default => false, :aliases => "-v", :desc => 'set loglevel as DEBUG'
77
- option :'more-verbose', :type => :boolean, :default => false, :desc => 'set loglevel as TRACE'
74
+ option :'more-quiet', type: :boolean, default: false, desc: 'set loglevel as ERROR'
75
+ option :quiet, type: :boolean, default: false, aliases: "-q", desc: 'set loglevel as WARN'
76
+ option :verbose, type: :boolean, default: false, aliases: "-v", desc: 'set loglevel as DEBUG'
77
+ option :'more-verbose', type: :boolean, default: false, desc: 'set loglevel as TRACE'
78
78
  }
79
79
  end
80
80
  end
@@ -117,7 +117,7 @@ module Norikra
117
117
  ### 'start' and 'serverprocess' have almost same option set (for parse/help)
118
118
  ### DIFF: jvm options (-X)
119
119
  Norikra::CLIUtil.register_common_start_options(self)
120
- option :help, :type => :boolean, :default => false, :aliases => "-h", :desc => "show this message"
120
+ option :help, type: :boolean, default: false, aliases: "-h", desc: "show this message"
121
121
  desc "start [-Xxxx] [other options]", "Start Norikra server process"
122
122
  def start(*optargs)
123
123
  if options[:help]
@@ -168,7 +168,7 @@ module Norikra
168
168
  File.open(outfile, 'w'){|file| file.write 'write test on parent process'}
169
169
 
170
170
  pidfile = File.open(options[:pidfile], 'w')
171
- pid = spawn(jruby_path, *args, :pgroup => 0)
171
+ pid = spawn(jruby_path, *args, pgroup: 0)
172
172
  pidfile.write(pid.to_s)
173
173
  pidfile.close
174
174
  waiting_child = true
@@ -183,12 +183,12 @@ module Norikra
183
183
  end
184
184
 
185
185
  Norikra::CLIUtil.register_common_start_options(self)
186
- desc "serverproc", "execute server process actually (don't execute this subcommand directly)", :hide => true
186
+ desc "serverproc", "execute server process actually (don't execute this subcommand directly)", hide: true
187
187
  def serverproc
188
188
  conf = {}
189
189
 
190
190
  if options[:daemonize]
191
- conf[:daemonize] = {:outfile => options[:outfile]}
191
+ conf[:daemonize] = {outfile: options[:outfile]}
192
192
  end
193
193
 
194
194
  ### stat file
@@ -247,9 +247,9 @@ module Norikra
247
247
  server.shutdown
248
248
  end
249
249
 
250
- option :pidfile, :type => :string, :default => DEFAULT_PID_PATH, :aliases => "-p", \
251
- :desc => "pidfile path when daemonized [#{DEFAULT_PID_PATH}]"
252
- option :timeout, :type => :numeric, :default => 5, :desc => "timeout seconds to wait process exit [5]"
250
+ option :pidfile, type: :string, default: DEFAULT_PID_PATH, aliases: "-p", \
251
+ desc: "pidfile path when daemonized [#{DEFAULT_PID_PATH}]"
252
+ option :timeout, type: :numeric, default: 5, desc: "timeout seconds to wait process exit [5]"
253
253
  desc "stop [options]", "stop daemonized Norikra server"
254
254
  def stop
255
255
  unless test(?r,options[:pidfile])
@@ -44,6 +44,9 @@ module Norikra
44
44
  @suspended_queries = []
45
45
 
46
46
  @waiting_queries = []
47
+
48
+ @listeners = []
49
+ @running_listeners = {} # query_name => listener
47
50
  end
48
51
 
49
52
  def statistics
@@ -109,7 +112,7 @@ module Norikra
109
112
 
110
113
  threads = t[sym][:threads].to_i
111
114
  capacity = t[sym][:capacity].to_i
112
- info "Engine #{sym} thread pool enabling", :threads => threads, :capacity => (capacity == 0 ? 'default' : capacity)
115
+ info "Engine #{sym} thread pool enabling", threads: threads, capacity: (capacity == 0 ? 'default' : capacity)
113
116
 
114
117
  cam = camelize(sym)
115
118
  threading.send("setThreadPool#{cam}".to_sym, true)
@@ -138,7 +141,7 @@ module Norikra
138
141
  def open(target_name, fields=nil, auto_field=true)
139
142
  # fields nil || [] => lazy
140
143
  # fields {'fieldname' => 'type'} : type 'string', 'boolean', 'int', 'long', 'float', 'double'
141
- info "opening target", :target => target_name, :fields => fields, :auto_field => auto_field
144
+ info "opening target", target: target_name, fields: fields, auto_field: auto_field
142
145
  raise Norikra::ArgumentError, "invalid target name" unless Norikra::Target.valid?(target_name)
143
146
  target = Norikra::Target.new(target_name, fields, auto_field)
144
147
  return false if @targets.include?(target)
@@ -146,7 +149,7 @@ module Norikra
146
149
  end
147
150
 
148
151
  def close(target_name)
149
- info "closing target", :target => target_name
152
+ info "closing target", target: target_name
150
153
  targets = @targets.select{|t| t.name == target_name}
151
154
  return false if targets.size != 1
152
155
  target = targets.first
@@ -157,7 +160,7 @@ module Norikra
157
160
  end
158
161
 
159
162
  def modify(target_name, auto_field)
160
- info "modify target", :target => target_name, :auto_field => auto_field
163
+ info "modify target", target: target_name, auto_field: auto_field
161
164
  targets = @targets.select{|t| t.name == target_name}
162
165
  if targets.size != 1
163
166
  raise Norikra::ArgumentError, "target name '#{target_name}' not found"
@@ -171,10 +174,12 @@ module Norikra
171
174
  end
172
175
 
173
176
  def register(query)
174
- info "registering query", :name => query.name, :targets => query.targets, :expression => query.expression
177
+ info "registering query", name: query.name, targets: query.targets, expression: query.expression
175
178
  raise Norikra::ClientError, "query name '#{query.name}' already exists" if @queries.select{|q| q.name == query.name }.size > 0
176
179
  raise Norikra::ClientError, "query name '#{query.name}' already exists in suspended" if @suspended_queries.select{|q| q.name == query.name }.size > 0
177
- raise Norikra::ClientError, "query '#{query.name}' is invalid query for Norikra" if query.invalid?
180
+ if reason = query.invalid?
181
+ raise Norikra::ClientError, "invalid query '#{query.name}': #{reason}"
182
+ end
178
183
 
179
184
  query.targets.each do |target_name|
180
185
  open(target_name) unless @targets.any?{|t| t.name == target_name}
@@ -183,7 +188,7 @@ module Norikra
183
188
  end
184
189
 
185
190
  def deregister(query_name)
186
- info "de-registering query", :name => query_name
191
+ info "de-registering query", name: query_name
187
192
  queries = @queries.select{|q| q.name == query_name }
188
193
  s_queries = @suspended_queries.select{|q| q.name == query_name }
189
194
 
@@ -242,12 +247,12 @@ module Norikra
242
247
  end
243
248
 
244
249
  def send(target_name, events)
245
- trace "send messages", :target => target_name, :events => events
250
+ trace "send messages", target: target_name, events: events
246
251
 
247
252
  @statistics[:events][:input] += events.size
248
253
 
249
254
  unless @targets.any?{|t| t.name == target_name} # discard events for target not registered
250
- trace "messages skipped for non-opened target", :target => target_name
255
+ trace "messages skipped for non-opened target", target: target_name
251
256
  return
252
257
  end
253
258
  return if events.size < 1
@@ -255,19 +260,19 @@ module Norikra
255
260
  target = @targets.select{|t| t.name == target_name}.first
256
261
 
257
262
  if @typedef_manager.lazy?(target.name)
258
- info "opening lazy target", :target => target
263
+ info "opening lazy target", target: target
259
264
 
260
265
  first_event = event_filter(events.first)
261
266
  if first_event.nil? # non-hash object
262
267
  raise Norikra::ClientError, "Input data must be JSON object"
263
268
  end
264
- debug "generating base fieldset from event", :target => target.name, :event => first_event
269
+ debug "generating base fieldset from event", target: target.name, event: first_event
265
270
  base_fieldset = @typedef_manager.generate_base_fieldset(target.name, first_event)
266
271
 
267
- debug "registering base fieldset", :target => target.name, :base => base_fieldset
272
+ debug "registering base fieldset", target: target.name, base: base_fieldset
268
273
  register_base_fieldset(target.name, base_fieldset)
269
274
 
270
- info "target successfully opened with fieldset", :target => target, :base => base_fieldset
275
+ info "target successfully opened with fieldset", target: target, base: base_fieldset
271
276
  end
272
277
 
273
278
  registered_data_fieldset = @registered_fieldsets[target_name][:data]
@@ -282,18 +287,19 @@ module Norikra
282
287
 
283
288
  unless registered_data_fieldset[fieldset.summary]
284
289
  # register waiting queries including this fieldset, and this fieldset itself
285
- debug "registering unknown fieldset", :target => target_name, :fieldset => fieldset
290
+ debug "registering unknown fieldset", target: target_name, fieldset: fieldset
286
291
  register_fieldset(target_name, fieldset)
287
292
  debug "successfully registered"
288
293
 
289
294
  # fieldset should be refined, when waiting_queries rewrite inheritance structure and data fieldset be renewed.
290
295
  fieldset = @typedef_manager.refer(target_name, event, strict_refer)
296
+ debug "re-referred data fieldset", target: target_name, fieldset: fieldset
291
297
  end
292
298
 
293
- trace "calling sendEvent with bound fieldset (w/ valid event_type_name)", :target => target_name, :event => event
294
- trace "This is assert for valid event_type_name", :event_type_name => fieldset.event_type_name
299
+ trace "calling sendEvent with bound fieldset (w/ valid event_type_name)", target: target_name, event: event
300
+ trace("This is assert for valid event_type_name"){ { event_type_name: fieldset.event_type_name } }
295
301
  formed = fieldset.format(event)
296
- trace "sendEvent", :data => formed
302
+ trace "sendEvent", data: formed
297
303
  @runtime.sendEvent(formed.to_java, fieldset.event_type_name)
298
304
  end
299
305
  target.update!
@@ -301,8 +307,13 @@ module Norikra
301
307
  nil
302
308
  end
303
309
 
304
- def load(plugin_klass)
305
- load_udf(plugin_klass)
310
+ def load(type, plugin_klass)
311
+ case type
312
+ when :udf then load_udf(plugin_klass)
313
+ when :listener then load_listener(plugin_klass)
314
+ else
315
+ raise "BUG: unknown plugin type: #{type}"
316
+ end
306
317
  end
307
318
 
308
319
  private
@@ -312,7 +323,7 @@ module Norikra
312
323
  return false if @targets.include?(target)
313
324
 
314
325
  @typedef_manager.add_target(target.name, target.fields)
315
- @registered_fieldsets[target.name] = {:base => {}, :query => {}, :data => {}}
326
+ @registered_fieldsets[target.name] = {base: {}, query: {}, data: {}}
316
327
 
317
328
  unless @typedef_manager.lazy?(target.name)
318
329
  base_fieldset = @typedef_manager.base_fieldset(target.name)
@@ -352,7 +363,7 @@ module Norikra
352
363
  def update_inherits_graph(target_name, query_fieldset)
353
364
  # replace registered data fieldsets with new fieldset inherits this query fieldset
354
365
  @typedef_manager.supersets(target_name, query_fieldset).each do |set|
355
- rebound = set.rebind(true) # update event_type_name with new inheritations
366
+ rebound = set.rebind(true, query_fieldset) # update event_type_name with new inheritations & nullable fields
356
367
 
357
368
  register_fieldset_actually(target_name, rebound, :data, true) # replacing on esper engine
358
369
  @typedef_manager.replace_fieldset(target_name, set, rebound)
@@ -362,26 +373,28 @@ module Norikra
362
373
 
363
374
  def register_query(query)
364
375
 
365
- if lo_target_name = Norikra::Query.loopback(query.group)
376
+ if lo_target_name = Norikra::Listener::Loopback.check(query.group)
366
377
  raise "Invalid loopback target name should be checked before. THIS IS BUG." unless Norikra::Target.valid?(lo_target_name)
367
378
 
368
379
  target = Norikra::Target.new(lo_target_name)
369
380
  unless @targets.include?(target)
370
- info "opening loopback target", :target => lo_target_name
381
+ info "opening loopback target", target: lo_target_name
371
382
  open_target(target)
372
383
  end
373
384
  end
374
385
 
375
386
  @mutex.synchronize do
376
387
  raise Norikra::ClientError, "query '#{query.name}' already exists" unless @queries.select{|q| q.name == query.name }.empty?
377
- raise Norikra::ClientError, "query '#{query.name}' is invalid query for Norikra" if query.invalid?
378
- if lo_target_name = Norikra::Query.loopback(query.group)
388
+ if reason = query.invalid?
389
+ raise Norikra::ClientError, "invalid query '#{query.name}': #{reason}"
390
+ end
391
+ if lo_target_name = Norikra::Listener::Loopback.check(query.group)
379
392
  raise Norikra::ClientError, "loopback target '#{lo_target_name}'" unless Norikra::Target.valid?(lo_target_name)
380
393
  end
381
394
 
382
395
  unless @typedef_manager.ready?(query)
383
396
  @waiting_queries.push(query)
384
- trace "waiting query fields", :targets => query.targets, :fields => query.targets.map{|t| query.fields(t)}
397
+ trace("waiting query fields"){ { targets: query.targets, fields: query.targets.map{|t| query.fields(t)} } }
385
398
  @typedef_manager.register_waiting_fields(query)
386
399
  @queries.push(query)
387
400
  return
@@ -389,7 +402,9 @@ module Norikra
389
402
 
390
403
  mapping = @typedef_manager.generate_fieldset_mapping(query)
391
404
  mapping.each do |target_name, query_fieldset|
405
+ trace "binding query fieldset", fieldset: query_fieldset
392
406
  @typedef_manager.bind_fieldset(target_name, :query, query_fieldset)
407
+ trace "registering query fieldset", fieldset: query_fieldset
393
408
  register_fieldset_actually(target_name, query_fieldset, :query)
394
409
  update_inherits_graph(target_name, query_fieldset)
395
410
  query.fieldsets[target_name] = query_fieldset
@@ -453,7 +468,9 @@ module Norikra
453
468
  ready.each do |query|
454
469
  mapping = @typedef_manager.generate_fieldset_mapping(query)
455
470
  mapping.each do |target_name, query_fieldset|
471
+ trace "binding query fieldset for waiting query", query: query, target: target_name, fieldset: query_fieldset
456
472
  @typedef_manager.bind_fieldset(target_name, :query, query_fieldset)
473
+ trace "registering query fieldset", target: target_name, fieldset: query_fieldset
457
474
  register_fieldset_actually(target_name, query_fieldset, :query)
458
475
  update_inherits_graph(target_name, query_fieldset)
459
476
  query.fieldsets[target_name] = query_fieldset
@@ -464,12 +481,30 @@ module Norikra
464
481
 
465
482
  def register_fieldset(target_name, fieldset)
466
483
  @mutex.synchronize do
484
+ trace "binding data fieldset", fieldset: fieldset # to prepare pickup waiting queries by newly comming fields
467
485
  @typedef_manager.bind_fieldset(target_name, :data, fieldset)
468
486
 
469
487
  if @waiting_queries.size > 0
470
488
  register_waiting_queries
471
489
  end
472
- debug "registering data fieldset", :target => target_name, :fields => fieldset.fields
490
+
491
+ diff_nullable_fields = []
492
+
493
+ @typedef_manager.subsets(target_name, fieldset).each do |query_fieldset|
494
+ next unless query_fieldset.level == :query
495
+ # fill nullable fields of all required query fieldsets
496
+ diff_nullable_fields += fieldset.nullable_diff(query_fieldset)
497
+ end
498
+
499
+ unless diff_nullable_fields.empty?
500
+ trace "query fieldset has nullable diff", diff: diff_nullable_fields
501
+ fieldset.update(diff_nullable_fields, true) # nullable fields are always optional
502
+ trace "rebinding data fieldset w/ nullable fields", fieldset: fieldset
503
+ rebound = fieldset.rebind(false) # type_name is not required to be updated because it is not registered yet
504
+ @typedef_manager.replace_fieldset(target_name, fieldset, rebound)
505
+ fieldset = rebound
506
+ end
507
+ debug "registering data fieldset", target: target_name, fieldset: fieldset
473
508
  register_fieldset_actually(target_name, fieldset, :data)
474
509
  end
475
510
  end
@@ -485,6 +520,10 @@ module Norikra
485
520
  end
486
521
  end
487
522
 
523
+ def load_listener(listener_klass)
524
+ @listeners.push(listener_klass)
525
+ end
526
+
488
527
  # this method should be protected with @mutex lock
489
528
  def register_query_actually(query, mapping)
490
529
  # 'mapping' argument is {target => fieldset}
@@ -498,13 +537,19 @@ module Norikra
498
537
  statement_model = administrator.compileEPL(query.expression)
499
538
  Norikra::Query.rewrite_query(statement_model, event_type_name_map)
500
539
 
501
- listener = if Norikra::Query.loopback(query.group)
502
- Norikra::LoopbackListener.new(self, query.name, query.group, @statistics[:events])
503
- elsif Norikra::Query.stdout?(query.group)
504
- Norikra::StdoutListener.new(self, query.name, query.group, @statistics[:events])
505
- else
506
- Norikra::Listener.new(query.name, query.group, @output_pool, @statistics[:events])
507
- end
540
+ listener = nil
541
+ @listeners.each do |klass|
542
+ trace("checking listeners"){ {target: klass, result: klass.check(query.group)} }
543
+ if klass.check(query.group)
544
+ listener = klass.new(query.name, query.group, @statistics[:events])
545
+ break
546
+ end
547
+ end
548
+ raise "BUG: no listener is selected" unless listener
549
+ listener.engine = self if listener.respond_to?(:engine=)
550
+ listener.output_pool = @output_pool if listener.respond_to?(:output_pool=)
551
+ listener.start
552
+ @running_listeners[query.name] = listener
508
553
 
509
554
  epl = administrator.create(statement_model)
510
555
  epl.java_send :addListener, [com.espertech.esper.client.UpdateListener.java_class], listener
@@ -522,9 +567,11 @@ module Norikra
522
567
  epl = administrator.getStatement(query.statement_name)
523
568
  return unless epl
524
569
 
525
- @output_pool.remove(query.name, query.group)
526
570
  epl.stop unless epl.isStopped
527
571
  epl.destroy unless epl.isDestroyed
572
+ listener = @running_listeners.delete(query.name)
573
+ listener.shutdown
574
+ @output_pool.remove(query.name, query.group)
528
575
  end
529
576
 
530
577
  # this method should be protected with @mutex lock
@@ -537,15 +584,15 @@ module Norikra
537
584
  # .addEventType("AccountUpdate", accountUpdateDef, new String[] {"BaseUpdate"});
538
585
  case level
539
586
  when :base
540
- debug "add event type", :target => target_name, :level => 'base', :event_type => fieldset.event_type_name
587
+ debug("add event type"){ { target: target_name, level: 'base', event_type: fieldset.event_type_name } }
541
588
  @config.addEventType(fieldset.event_type_name, fieldset.definition)
542
589
  when :query
543
590
  base_name = @typedef_manager.base_fieldset(target_name).event_type_name
544
- debug "add event type", :target => target_name, :level => 'query', :event_type => fieldset.event_type_name, :base => base_name
591
+ debug("add event type"){ { target: target_name, level: 'query', event_type: fieldset.event_type_name, base: base_name } }
545
592
  @config.addEventType(fieldset.event_type_name, fieldset.definition, [base_name].to_java(:string))
546
- else
593
+ else # :data
547
594
  subset_names = @typedef_manager.subsets(target_name, fieldset).map(&:event_type_name)
548
- debug "add event type", :target => target_name, :level => 'data', :event_type => fieldset.event_type_name, :inherit => subset_names
595
+ debug("add event type"){ { target: target_name, level: 'data', event_type: fieldset.event_type_name, inherit: subset_names } }
549
596
  @config.addEventType(fieldset.event_type_name, fieldset.definition, subset_names.to_java(:string))
550
597
 
551
598
  @registered_fieldsets[target_name][level][fieldset.summary] = fieldset
@@ -559,7 +606,7 @@ module Norikra
559
606
 
560
607
  # DON'T check @registered_fieldsets[target_name][level][fieldset.summary]
561
608
  # removed fieldset should be already replaced with register_fieldset_actually w/ replace flag
562
- debug "remove event type", :target => target_name, :event_type => event_type_name
609
+ debug "remove event type", target: target_name, event_type: event_type_name
563
610
  @config.removeEventType(event_type_name, true)
564
611
  end
565
612
 
@@ -567,7 +614,7 @@ module Norikra
567
614
  FILTER_OPTIMIZABLE_ENUM = com.espertech.esper.client.ConfigurationPlugInSingleRowFunction::FilterOptimizable
568
615
 
569
616
  def load_udf_actually(udf)
570
- debug "importing class into config object", :name => udf.class.to_s
617
+ debug "importing class into config object", name: udf.class.to_s
571
618
 
572
619
  functionName, className, methodName = udf.definition
573
620
 
@@ -575,17 +622,17 @@ module Norikra
575
622
  filterOptimizable = udf.filter_optimizable ? FILTER_OPTIMIZABLE_ENUM::ENABLED : FILTER_OPTIMIZABLE_ENUM::DISABLED
576
623
  rethrowExceptions = udf.rethrow_exceptions
577
624
 
578
- debug "adding SingleRowFunction", :function => functionName, :javaClass => className, :javaMethod => methodName
625
+ debug "adding SingleRowFunction", function: functionName, javaClass: className, javaMethod: methodName
579
626
  @config.addPlugInSingleRowFunction(functionName, className, methodName, valueCache, filterOptimizable, rethrowExceptions)
580
627
  functionName
581
628
  end
582
629
 
583
630
  def load_udf_aggregation_actually(udf)
584
- debug "importing class into config object", :name => udf.class.to_s
631
+ debug "importing class into config object", name: udf.class.to_s
585
632
 
586
633
  functionName, factoryClassName = udf.definition
587
634
 
588
- debug "adding AggregationSingleFactory", :function => functionName, :javaClass => factoryClassName
635
+ debug "adding AggregationSingleFactory", function: functionName, javaClass: factoryClassName
589
636
  @config.addPlugInAggregationFunctionFactory(functionName, factoryClassName)
590
637
  functionName
591
638
  end