norikra 1.1.2-java → 1.2.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/norikra/field.rb CHANGED
@@ -35,7 +35,7 @@ module Norikra
35
35
  ### expected java.lang.Class or java.util.Map or the name of a previously-declared Map or ObjectArray type
36
36
  #### Correct type name is 'int'. see and run 'junks/esper-test.rb'
37
37
 
38
- attr_accessor :name, :type, :esper_type, :optional, :escaped_name, :container_name, :container_type
38
+ attr_accessor :name, :type, :esper_type, :optional, :nullable, :escaped_name, :container_name, :container_type
39
39
 
40
40
  def self.esper_type_map(type)
41
41
  case type.to_s.downcase
@@ -75,10 +75,11 @@ module Norikra
75
75
  end
76
76
  end
77
77
 
78
- def initialize(name, type, optional=nil)
78
+ def initialize(name, type, optional=nil, nullable=false)
79
79
  @name = name.to_s
80
80
  @type = self.class.valid_type(type)
81
81
  @optional = optional
82
+ @nullable = nullable
82
83
 
83
84
  @escaped_name = self.class.escape_name(@name)
84
85
 
@@ -154,24 +155,28 @@ module Norikra
154
155
 
155
156
  def to_hash(sym=false)
156
157
  if sym
157
- {name: @name, type: @type, optional: @optional}
158
+ {name: @name, type: @type, optional: @optional, nullable: @nullable}
158
159
  else
159
- {'name' => @name, 'type' => @type, 'optional' => @optional}
160
+ {'name' => @name, 'type' => @type, 'optional' => @optional, 'nullable' => @nullable}
160
161
  end
161
162
  end
162
163
 
163
164
  def dup(optional=nil)
164
- self.class.new(@name, @type, optional.nil? ? @optional : optional)
165
+ self.class.new(@name, @type, optional.nil? ? @optional : optional, @nullable)
165
166
  end
166
167
 
167
168
  def ==(other)
168
- self.name == other.name && self.type == other.type && self.optional == other.optional
169
+ self.name == other.name && self.type == other.type && self.optional == other.optional && self.nullable == other.nullable
169
170
  end
170
171
 
171
172
  def optional? # used outside of FieldSet
172
173
  @optional
173
174
  end
174
175
 
176
+ def nullable?
177
+ @nullable
178
+ end
179
+
175
180
  # def value(event) # by define_value_accessor
176
181
 
177
182
  def format(value, element_path=nil) #element_path ex: 'fname.fchild', 'fname.$0', 'f.fchild.$2'
@@ -228,7 +233,7 @@ module Norikra
228
233
  self.instance_eval do
229
234
  def safe_fetch(v, accessor)
230
235
  unless accessor.is_a?(String) || accessor.is_a?(Fixnum)
231
- raise ArgumentError, "container_accessor must be a String or Interger, but #{accessor.class.to_s}"
236
+ raise ::ArgumentError, "container_accessor must be a String or Interger, but #{accessor.class.to_s}"
232
237
  end
233
238
  if v.is_a?(Hash)
234
239
  # v[accessor] || v[accessor.to_s]
@@ -17,11 +17,11 @@ module Norikra
17
17
  elsif data.is_a?(Hash)
18
18
  type = data[:type].to_s
19
19
  optional = data.has_key?(:optional) ? data[:optional] : default_optional
20
- @fields[key.to_s] = Field.new(key.to_s, type, optional)
20
+ @fields[key.to_s] = Field.new(key.to_s, type, optional, !!data[:nullable])
21
21
  elsif data.is_a?(String) || data.is_a?(Symbol)
22
22
  @fields[key.to_s] = Field.new(key.to_s, data.to_s, default_optional)
23
23
  else
24
- raise ArgumentError, "FieldSet.new argument class unknown: #{fields.class}"
24
+ raise ::ArgumentError, "FieldSet.new argument class unknown: #{fields.class}"
25
25
  end
26
26
  end
27
27
  self.update_summary
@@ -34,7 +34,7 @@ module Norikra
34
34
  end
35
35
 
36
36
  def dup
37
- fields = Hash[@fields.map{|key,field| [key, {:type => field.type, :optional => field.optional}]}]
37
+ fields = Hash[@fields.map{|key,field| [key, {type: field.type, optional: field.optional, nullable: field.nullable?}]}]
38
38
  self.class.new(fields, nil, @rebounds, @query_unique_keys)
39
39
  end
40
40
 
@@ -75,13 +75,16 @@ module Norikra
75
75
  dig.call(container)
76
76
  end
77
77
 
78
+ #### field_names_key is for lookup FieldSet from event data
79
+ # field_names_key: a,b,c,d
80
+ ### comma separated field names, which contains valid values (null fields are not included)
78
81
  def self.field_names_key(data, fieldset=nil, strict=false, additional_fields=[])
79
82
  if !fieldset && strict
80
83
  raise RuntimeError, "strict(true) cannot be specified with fieldset=nil"
81
84
  end
82
85
 
83
86
  unless fieldset
84
- return data.keys.sort.join(',')
87
+ return data.reject{|k,v| v.respond_to?(:nullable?) && v.nullable? }.keys.sort.join(',')
85
88
  end
86
89
 
87
90
  keys = []
@@ -113,8 +116,9 @@ module Norikra
113
116
  self.class.field_names_key(@fields)
114
117
  end
115
118
 
119
+ # summary is for checks whether FieldSet is already registered or not
116
120
  def update_summary
117
- @summary = @fields.keys.sort.map{|k| @fields[k].escaped_name + ':' + @fields[k].type}.join(',')
121
+ @summary = @fields.keys.sort.map{|k| f = @fields[k]; "#{f.escaped_name}:#{f.type}" + (f.nullable? ? ':nullable' : '')}.join(',')
118
122
  self
119
123
  end
120
124
 
@@ -125,7 +129,10 @@ module Norikra
125
129
  self.update_summary
126
130
  end
127
131
 
128
- #TODO: have a bug?
132
+ def nullable_diff(fieldset) # data_fieldset.nullable_diff(query_fieldset)
133
+ fieldset.fields.select{|fname, f| !self.fields[fname] && f.nullable? }.values
134
+ end
135
+
129
136
  def ==(other)
130
137
  return false if self.class != other.class
131
138
  self.summary == other.summary && self.query_unique_keys == other.query_unique_keys
@@ -139,15 +146,16 @@ module Norikra
139
146
  d
140
147
  end
141
148
 
142
- def subset?(other) # self is subset of other (or not)
143
- (self.fields.keys - other.fields.keys).size == 0
149
+ def subset?(other) # self is subset of other (or not) ### query_fieldset.subset?(fieldset_referred_from_event_data)
150
+ ### nullable fields can be ignored (to be updated later, with nullable fields ignored here)
151
+ (self.fields.keys - other.fields.keys).reject{|fname| @fields[fname].nullable? }.size < 1
144
152
  end
145
153
 
146
154
  def event_type_name
147
155
  @event_type_name.dup
148
156
  end
149
157
 
150
- def bind(target, level, update_type_name=false)
158
+ def bind(target, level, type_name_update=false)
151
159
  @target = target
152
160
  @level = level
153
161
  prefix = case level
@@ -155,17 +163,22 @@ module Norikra
155
163
  when :query then 'q_'
156
164
  when :data then 'e_' # event
157
165
  else
158
- raise ArgumentError, "unknown fieldset bind level: #{level}, for target #{target}"
166
+ raise ::ArgumentError, "unknown fieldset bind level: #{level}, for target #{target}"
159
167
  end
160
- @rebounds += 1 if update_type_name
168
+ @rebounds += 1 if type_name_update
161
169
  query_unique_key = @query_unique_keys ? @query_unique_keys.join("\t") : ''
162
170
 
163
171
  @event_type_name = prefix + Digest::MD5.hexdigest([target, level.to_s, @rebounds.to_s, query_unique_key, @summary].join("\t"))
164
172
  self
165
173
  end
166
174
 
167
- def rebind(update_type_name)
168
- self.dup.bind(@target, @level, update_type_name)
175
+ def rebind(type_name_update, query_fieldset=nil)
176
+ renew = self.dup
177
+ if query_fieldset
178
+ diff = self.nullable_diff(query_fieldset)
179
+ renew.update(diff, true) unless diff.empty? # all nullable fields are optional
180
+ end
181
+ renew.bind(@target, @level, type_name_update)
169
182
  end
170
183
 
171
184
  def format(data)
@@ -4,90 +4,221 @@ require 'esper/lib/commons-logging-1.1.3.jar'
4
4
  require 'esper/lib/antlr-runtime-4.1.jar'
5
5
  require 'esper/lib/cglib-nodep-3.1.jar'
6
6
 
7
+ require 'rubygems'
8
+
7
9
  require 'norikra/field'
8
10
  require 'norikra/query'
9
11
 
12
+ require 'norikra/logger'
13
+ include Norikra::Log
14
+
10
15
  require 'json'
11
16
 
12
17
  module Norikra
13
- class Listener
14
- include com.espertech.esper.client.UpdateListener
15
-
16
- def initialize(query_name, query_group, output_pool, events_statistics)
17
- @query_name = query_name
18
- @query_group = query_group
19
- @output_pool = output_pool
20
- @events_statistics = events_statistics
18
+ module Listener
19
+ def self.listup
20
+ return unless defined? Gem
21
+
22
+ plugins = Gem.find_latest_files('norikra/listener/*.rb')
23
+ plugins.each do |plugin|
24
+ begin
25
+ debug "plugin file found!", file: plugin
26
+ rbpath = plugin.dup
27
+ 4.times do
28
+ rbpath = File.dirname( rbpath )
29
+ end
30
+ files = Dir.entries( rbpath )
31
+ gemname = files.select{|f| f=~ /\.gemspec$/ }.first.sub(/\.gemspec$/, '')
32
+ trace "Loading listener gem", gemname: gemname, path: plugin
33
+ require gemname
34
+ load plugin
35
+ rescue => e
36
+ warn "Failed to load norikra listener plugin", plugin: plugin.to_s, error_class: e.class, error: e.message
37
+ e.backtrace.each do |t|
38
+ warn " " + t
39
+ end
40
+ end
41
+ end
42
+
43
+ known_consts = [:Base, :MemoryPool, :Loopback, :Stdout]
44
+ listeners = [Norikra::Listener::Stdout, Norikra::Listener::Loopback]
45
+ self.constants.each do |c|
46
+ next if known_consts.include?(c)
47
+
48
+ klass = Norikra::Listener.const_get(c)
49
+ if klass.is_a?(Class) && klass.superclass == Norikra::Listener::Base
50
+ listeners.push(klass)
51
+ end
52
+ end
53
+ listeners.push(Norikra::Listener::MemoryPool)
54
+ listeners
21
55
  end
22
56
 
23
- def type_convert(value)
24
- if value.respond_to?(:getUnderlying)
25
- value = value.getUnderlying
57
+ class Base
58
+ include com.espertech.esper.client.UpdateListener
59
+
60
+ DEFAULT_ASYNC_INTERVAL = 0.1
61
+
62
+ def self.check(group_name)
63
+ raise NotImplementedError
26
64
  end
27
65
 
28
- trace "converting", :value => value
66
+ def initialize(query_name, query_group, events_statistics)
67
+ @query_name = query_name
68
+ @query_group = query_group
69
+ @events_statistics = events_statistics
29
70
 
30
- if value.nil?
31
- value
32
- elsif value.respond_to?(:to_hash)
33
- Hash[ value.to_hash.map{|k,v| [ Norikra::Field.unescape_name(k), type_convert(v)] } ]
34
- elsif value.respond_to?(:to_a)
35
- value.to_a.map{|v| type_convert(v) }
36
- elsif value.respond_to?(:force_encoding)
37
- value.force_encoding('UTF-8')
38
- else
39
- value
71
+ @async_interval = DEFAULT_ASYNC_INTERVAL
72
+
73
+ @thread = nil
74
+ @events = []
75
+ @mutex = Mutex.new
76
+ @running = true
40
77
  end
41
- end
42
78
 
43
- def update(new_events, old_events)
44
- t = Time.now.to_i
45
- events = new_events.map{|e| [t, type_convert(e)]}
46
- trace "updated event", :query => @query_name, :group => @query_group, :event => events
47
- @output_pool.push(@query_name, @query_group, events)
48
- @events_statistics[:output] += events.size
49
- end
50
- end
79
+ # def engine=(engine)
80
+ # @engine = engine
81
+ # end
82
+
83
+ # def output_pool=(output_pool)
84
+ # @output_pool = output_pool
85
+ # end
86
+
87
+ def start
88
+ if self.respond_to?(:process_async)
89
+ trace "starting thread to process events in background", query: @query_name
90
+ @thread = Thread.new(&method(:background))
91
+ end
92
+ end
93
+
94
+ def background
95
+ trace "backgroupd thread starts", query: @query_name
96
+ while @running
97
+ events_empty = true
98
+ events = nil
99
+ @mutex.synchronize do
100
+ events = @events
101
+ @events = []
102
+ end
103
+ unless events.empty?
104
+ events_empty = false
105
+ trace("calling #process_async"){ {listener: self.class, query: @query_name, events: events.size} }
106
+ process_async(events)
107
+ end
108
+ sleep @async_interval if events_empty
109
+ end
110
+ rescue => e
111
+ error "exception in listener background thread, stopped", listener: self.class, query: @query_name, error: e
112
+ end
113
+
114
+ def push(events)
115
+ @mutex.synchronize do
116
+ @events += events
117
+ end
118
+ end
119
+
120
+ # def process_async
121
+ # end
51
122
 
52
- class LoopbackListener < Listener
53
- def initialize(engine, query_name, query_group, events_statistics)
54
- @engine = engine
55
- @query_name = query_name
56
- @query_group = query_group
57
- @events_statistics = events_statistics
58
- @loopback_target = Norikra::Query.loopback(query_group)
123
+ def shutdown
124
+ trace "stopping listener", query: @query_name
125
+ @running = false
126
+ @thread.join if @thread
127
+ @thread = nil
128
+ end
129
+
130
+ def type_convert(value)
131
+ if value.respond_to?(:getUnderlying)
132
+ value = value.getUnderlying
133
+ end
134
+
135
+ trace("converting"){ { value: value } }
136
+
137
+ if value.nil?
138
+ value
139
+ elsif value.respond_to?(:to_hash)
140
+ Hash[ value.to_hash.map{|k,v| [ Norikra::Field.unescape_name(k), type_convert(v)] } ]
141
+ elsif value.respond_to?(:to_a)
142
+ value.to_a.map{|v| type_convert(v) }
143
+ elsif value.respond_to?(:force_encoding)
144
+ value.force_encoding('UTF-8')
145
+ else
146
+ value
147
+ end
148
+ end
149
+
150
+ def update(new_events, old_events)
151
+ t = Time.now.to_i
152
+ events = new_events.map{|e| [t, type_convert(e)]}
153
+ trace("updated event"){ { query: @query_name, group: @query_group, event: events } }
154
+ push(events)
155
+ @events_statistics[:output] += events.size
156
+ end
59
157
  end
60
158
 
61
- def update(new_events, old_events)
62
- event_list = new_events.map{|e| type_convert(e) }
63
- trace "loopback event", :query => @query_name, :group => @query_group, :event => event_list
64
- @events_statistics[:output] += event_list.size
65
- #
66
- # We does NOT convert 'container.$0' into container['field'].
67
- # Use escaped names like 'container__0'. That is NOT so confused.
68
- @engine.send(@loopback_target, event_list)
159
+ class MemoryPool < Base
160
+ def self.check(group_name)
161
+ true
162
+ end
163
+
164
+ def output_pool=(output_pool)
165
+ @output_pool = output_pool
166
+ end
167
+
168
+ def update(new_events, old_events)
169
+ t = Time.now.to_i
170
+ events = new_events.map{|e| [t, type_convert(e)]}
171
+ trace("updated event"){ { query: @query_name, group: @query_group, event: events } }
172
+ @output_pool.push(@query_name, @query_group, events)
173
+ @events_statistics[:output] += events.size
174
+ end
69
175
  end
70
- end
71
176
 
72
- class StdoutListener < Listener
73
- def initialize(engine, query_name, query_group, events_statistics)
74
- raise "BUG: query group is not 'STDOUT()'" unless query_group == 'STDOUT()'
177
+ class Loopback < Base
178
+ def self.check(group_name)
179
+ group_name && group_name =~ /^LOOPBACK\((.+)\)$/ && $1
180
+ end
75
181
 
76
- @engine = engine
77
- @query_name = query_name
78
- @query_group = query_group
79
- @events_statistics = events_statistics
182
+ def initialize(query_name, query_group, events_statistics)
183
+ super
184
+ @loopback_target = Loopback.check(query_group)
185
+ raise "BUG: query group is not 'LOOPBACK(...)'" unless @loopback_target
186
+ end
187
+
188
+ def engine=(engine)
189
+ @engine = engine
190
+ end
80
191
 
81
- @stdout = STDOUT
192
+ def update(new_events, old_events)
193
+ event_list = new_events.map{|e| type_convert(e) }
194
+ trace("loopback event"){ { query: @query_name, group: @query_group, event: event_list } }
195
+ @events_statistics[:output] += event_list.size
196
+ #
197
+ # We does NOT convert 'container.$0' into container['field'].
198
+ # Use escaped names like 'container__0'. That is NOT so confused.
199
+ @engine.send(@loopback_target, event_list)
200
+ end
82
201
  end
83
202
 
84
- def update(new_events, old_events)
85
- event_list = new_events.map{|e| type_convert(e) }
86
- trace "stdout event", :query => @query_name, :event => event_list
87
- @events_statistics[:output] += event_list.size
203
+ class Stdout < Base
204
+ def self.check(group_name)
205
+ group_name && group_name == "STDOUT()"
206
+ end
207
+
208
+ def initialize(query_name, query_group, events_statistics)
209
+ super
210
+ raise "BUG: query group is not 'STDOUT()'" unless Stdout.check(query_group)
211
+ @stdout = STDOUT
212
+ end
213
+
214
+ def update(new_events, old_events)
215
+ event_list = new_events.map{|e| type_convert(e) }
216
+ trace("stdout event"){ { query: @query_name, event: event_list } }
217
+ @events_statistics[:output] += event_list.size
88
218
 
89
- event_list.each do |e|
90
- @stdout.puts @query_name + "\t" + JSON.dump(e)
219
+ event_list.each do |e|
220
+ @stdout.puts @query_name + "\t" + JSON.dump(e)
221
+ end
91
222
  end
92
223
  end
93
224
  end
@@ -55,7 +55,7 @@ module Norikra
55
55
  end
56
56
 
57
57
  level = level.upcase
58
- raise ArgumentError, "unknown log level: #{level}" unless LOG_LEVELS.include?(level)
58
+ raise ::ArgumentError, "unknown log level: #{level}" unless LOG_LEVELS.include?(level)
59
59
 
60
60
  p = java.util.Properties.new
61
61
  p.setProperty('log4j.appender.default.layout', 'org.apache.log4j.PatternLayout')
@@ -122,32 +122,44 @@ module Norikra
122
122
 
123
123
  def self.logger; @@logger ; end
124
124
 
125
- def trace(message, data=nil)
125
+ def trace(message, data=nil, &block)
126
+ return unless @@logger.enabled?(LEVEL_TRACE)
127
+ data ||= block
126
128
  from = @@devmode ? caller_locations(1,1) : nil
127
129
  @@logger.trace(message, data, from)
128
130
  end
129
131
 
130
- def debug(message, data=nil)
132
+ def debug(message, data=nil, &block)
133
+ return unless @@logger.enabled?(LEVEL_DEBUG)
134
+ data ||= block
131
135
  from = @@devmode ? caller_locations(1,1) : nil
132
136
  @@logger.debug(message, data, from)
133
137
  end
134
138
 
135
- def info(message, data=nil)
139
+ def info(message, data=nil, &block)
140
+ return unless @@logger.enabled?(LEVEL_INFO)
141
+ data ||= block
136
142
  from = @@devmode ? caller_locations(1,1) : nil
137
143
  @@logger.info(message, data, from)
138
144
  end
139
145
 
140
- def warn(message, data=nil)
146
+ def warn(message, data=nil, &block)
147
+ return unless @@logger.enabled?(LEVEL_WARN)
148
+ data ||= block
141
149
  from = @@devmode ? caller_locations(1,1) : nil
142
150
  @@logger.warn(message, data, from)
143
151
  end
144
152
 
145
- def error(message, data=nil)
153
+ def error(message, data=nil, &block)
154
+ return unless @@logger.enabled?(LEVEL_ERROR)
155
+ data ||= block
146
156
  from = @@devmode ? caller_locations(1,1) : nil
147
157
  @@logger.error(message, data, from)
148
158
  end
149
159
 
150
- def fatal(message, data=nil)
160
+ def fatal(message, data=nil, &block)
161
+ # always enabled
162
+ data ||= block
151
163
  from = @@devmode ? caller_locations(1,1) : nil
152
164
  @@logger.fatal(message, data, from)
153
165
  end
@@ -172,6 +184,17 @@ module Norikra
172
184
  @buffer << [Time.now.strftime(TIME_FORMAT), level, line]
173
185
  end
174
186
 
187
+ def enabled?(level)
188
+ case level
189
+ when LEVEL_TRACE then @log4j.isTraceEnabled
190
+ when LEVEL_DEBUG then @log4j.isDebugEnabled
191
+ when LEVEL_INFO then @log4j.isInfoEnabled
192
+ when LEVEL_WARN then @log4j.isWarnEnabled
193
+ when LEVEL_ERROR then @log4j.isErrorEnabled
194
+ else true
195
+ end
196
+ end
197
+
175
198
  def trace(message, data=nil, from=nil)
176
199
  return unless @log4j.isTraceEnabled
177
200
  line = format(from, message, data)
@@ -225,7 +248,7 @@ module Norikra
225
248
 
226
249
  #, status:404, message:'content not found'
227
250
  if data.is_a?(Proc)
228
- ', ' + format_data(data.call)
251
+ format_data(data.call)
229
252
  elsif data.is_a?(Hash)
230
253
  ', ' + data.map{|k,v| "#{k}:#{v.inspect}"}.join(', ')
231
254
  else
@@ -245,14 +268,15 @@ module Norikra
245
268
  FORMAT_SIMULATED = "%s [%s] %s\n"
246
269
  attr_accessor :logs, :output
247
270
  def initialize
248
- @logs = { :TRACE => [], :DEBUG => [], :INFO => [], :WARN => [], :ERROR => [], :FATAL => [] }
271
+ @logs = { TRACE: [], DEBUG: [], INFO: [], WARN: [], ERROR: [], FATAL: [] }
249
272
  @output = []
250
273
  end
251
274
  def log(level, message, data, from)
252
- @logs[level].push({:message => message, :data => data, :from => from})
275
+ @logs[level].push({message: message, data: data, from: from})
253
276
  formatted = sprintf(FORMAT_SIMULATED, Time.now.strftime(TIME_FORMAT), level.to_s, format(from, message, data))
254
277
  @output.push(formatted)
255
278
  end
279
+ def enabled?(level); true; end
256
280
  def trace(m,d,f); self.log(:TRACE,m,d,f); end
257
281
  def debug(m,d,f); self.log(:DEBUG,m,d,f); end
258
282
  def info(m,d,f) ; self.log(:INFO, m,d,f); end
@@ -21,7 +21,7 @@ module Norikra
21
21
  group.delete(query_name)
22
22
  end
23
23
  @groups.delete(query_name)
24
- @pool.delete(query_name)
24
+ @pool.delete(query_name)
25
25
  end
26
26
  nil
27
27
  end