gd_es 0.0.2

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.
@@ -0,0 +1,1077 @@
1
+ require 'pry'
2
+ require 'chronic'
3
+ require 'jsonify'
4
+ require 'json'
5
+ require 'rainbow'
6
+ require 'yajl'
7
+ require 'active_support/time'
8
+ require 'active_support/ordered_hash'
9
+ require 'terminal-table'
10
+ require 'pathname'
11
+ require 'tempfile'
12
+ require 'commands'
13
+
14
+ module Es
15
+
16
+ class InsufficientSpecificationError < RuntimeError
17
+ end
18
+
19
+ class IncorrectSpecificationError < RuntimeError
20
+ end
21
+
22
+ class UnableToMerge < RuntimeError
23
+ end
24
+
25
+ class Timeframe
26
+ INTERVAL_UNITS = [:day, :week, :month, :year]
27
+ DAY_WITHIN_PERIOD = [:first, :last]
28
+ attr_accessor :to, :from, :interval_unit, :interval, :day_within_period, :spec_from, :spec_to
29
+
30
+ def self.parse(spec, options={})
31
+ if spec == 'latest' then
32
+ Timeframe.new({
33
+ :to => 'tomorrow',
34
+ :from => 'yesterday'
35
+ }, options)
36
+ else
37
+ Timeframe.new(spec, options)
38
+ end
39
+ end
40
+
41
+ def self.parseOldFormat(spec, options={})
42
+ if spec == 'latest' then
43
+ Timeframe.new({
44
+ :to => 'today',
45
+ :from => 'yesterday'
46
+ })
47
+ else
48
+ Timeframe.new({
49
+ :to => spec[:endDate],
50
+ :from => spec[:startDate],
51
+ :interval_unit => spec[:intervalUnit],
52
+ :interval => spec[:interval],
53
+ :day_within_period => spec[:dayWithinPeriod].downcase
54
+ })
55
+ end
56
+ end
57
+
58
+ def initialize(spec,options = {})
59
+ validate_spec(spec)
60
+ @now = options[:now] || Time.now
61
+ @spec = spec
62
+ @to = Chronic.parse(spec[:to], :now => @now)
63
+ @from = spec[:from] ? Chronic.parse(spec[:from], :now => @now) : to.advance(:days => -1)
64
+ @spec_from = spec[:from]
65
+ @spec_to = spec[:to]
66
+ @interval_unit = spec[:interval_unit] || :day
67
+ @interval = spec[:interval] || 1
68
+ @day_within_period = spec[:day_within_period] || :last
69
+ end
70
+
71
+
72
+ def validate_spec(spec)
73
+ fail IncorrectSpecificationError.new("Timeframe should have a specification") if spec.nil?
74
+ fail InsufficientSpecificationError.new("To key was not specified during the Timeframe creation") unless spec.has_key?(:to)
75
+ fail InsufficientSpecificationError.new("From key was not specified during the Timeframe creation") unless spec.has_key?(:from)
76
+ fail IncorrectSpecificationError.new("Interval key should be a number") if spec[:interval] && !spec[:interval].is_a?(Fixnum)
77
+ fail IncorrectSpecificationError.new("Interval_unit key should be one of :day, :week, :month, :year") if spec[:interval_unit] && !INTERVAL_UNITS.include?(spec[:interval_unit].to_sym)
78
+ fail IncorrectSpecificationError.new("Day within period should be one of #{DAY_WITHIN_PERIOD.join(', ')}") if spec[:day_within_period] && !DAY_WITHIN_PERIOD.include?(spec[:day_within_period].to_sym)
79
+ end
80
+
81
+ def to_extract_fragment(pid, options = {})
82
+ {
83
+ :endDate => to.strftime('%Y-%m-%d'),
84
+ :startDate => from.strftime('%Y-%m-%d'),
85
+ :intervalUnit => interval_unit,
86
+ :dayWithinPeriod => day_within_period.to_s.upcase,
87
+ :interval => interval
88
+ }
89
+ end
90
+
91
+
92
+ def to_config_generator_extract
93
+ {
94
+ :from => from.strftime('%Y-%m-%d'),
95
+ :to => to.strftime('%Y-%m-%d'),
96
+ :interval_unit => interval_unit,
97
+ :interval => day_within_period.to_s.upcase,
98
+ :day_within_period => interval
99
+ }
100
+ end
101
+
102
+ end
103
+
104
+ class Extract
105
+
106
+ attr_accessor :entities, :timeframe, :timezone, :partial
107
+
108
+ def self.parse(spec, a_load, options={})
109
+ global_timeframe = parse_timeframes(spec[:timeframes], options) || parse_timeframes("latest", options)
110
+ timezone = spec[:timezone]
111
+ parsed_entities = spec[:entities].map do |entity_spec|
112
+ entity_name = entity_spec[:entity]
113
+ partial = entity_spec[:partial] || "false"
114
+ load_entity = a_load.get_merged_entity_for(entity_name)
115
+ fields = entity_spec[:fields].map do |field|
116
+ if load_entity.has_field?(field)
117
+ if (load_entity.get_field(field).is_recordid?)
118
+ Es::RecordIdField.new(load_entity.get_field(field).name, load_entity.get_field(field).type, partial)
119
+ else
120
+ load_entity.get_field(field)
121
+ end
122
+ elsif field == "DeletedAt"
123
+ Es::Field.new("DeletedAt", "time")
124
+ elsif field == "IsDeleted"
125
+ Es::Field.new("IsDeleted", "attribute")
126
+ elsif field == "snapshot"
127
+ Es::SnapshotField.new("snapshot", "snapshot")
128
+ elsif field == "autoincrement"
129
+ Es::AutoincrementField.new("generate", "autoincrement")
130
+ elsif field == "duration" || (field.respond_to?(:keys) && field.keys.first == :duration )
131
+ if (field == "duration") then
132
+ Es::DurationField.new("duration", "duration")
133
+ else
134
+ Es::DurationField.new("duration", "duration",{
135
+ :attribute => field[:duration][:attribute],
136
+ :value => field[:duration][:value],
137
+ :control_attribute => field[:duration][:name]
138
+ })
139
+ end
140
+ elsif field == "velocity" || (field.respond_to?(:keys) && field.keys.first == :velocity )
141
+ if (field == "velocity") then
142
+ Es::VelocityField.new("velocity", "velocity")
143
+ else
144
+ Es::VelocityField.new("velocity", "velocity",{
145
+ :control_attribute => field[:velocity][:name]
146
+ })
147
+ end
148
+ elsif field.respond_to?(:keys) && field.keys.first == :hid
149
+ Es::HIDField.new('hid', "historicid", {
150
+ :entity => field[:hid][:from_entity],
151
+ :fields => field[:hid][:from_fields],
152
+ :through => field[:hid][:connected_through]
153
+ })
154
+ else
155
+ fail InsufficientSpecificationError.new("The field #{field.to_s.bright} was not found in either the loading specification nor was recognized as a special column")
156
+ end
157
+ end
158
+ parsed_timeframe = parse_timeframes(entity_spec[:timeframes], options)
159
+ Entity.new(entity_name, {
160
+ :fields => fields,
161
+ :file => Pathname.new(entity_spec[:file]).expand_path.to_s,
162
+ :timeframe => parsed_timeframe || global_timeframe || (fail "Timeframe has to be defined"),
163
+ :timezone => timezone
164
+ })
165
+ end
166
+
167
+ Extract.new(parsed_entities)
168
+ end
169
+
170
+
171
+ def self.parseOldFormat(spec,a_load)
172
+ global_timeframe = parseOldFormat_timeframes(spec[:timeFrames])
173
+ timezone = spec[:timezone]
174
+ entity_name = spec[:entity]
175
+ load_entity = a_load.get_merged_entity_for(entity_name)
176
+ parser = Yajl::Parser.new(:symbolize_keys => true)
177
+ i=0
178
+ begin
179
+ doc = parser.parse(spec[:readMap])
180
+ doc.map do |internal|
181
+ fields = internal[:columns].map do |definition|
182
+ if load_entity.has_field?(definition[:name])
183
+ load_entity.get_field(definition[:name])
184
+ elsif definition[:name] == "DeletedAt"
185
+ Es::Field.new("DeletedAt", "time")
186
+ elsif definition[:name] == "IsDeleted"
187
+ Es::Field.new("IsDeleted", "attribute")
188
+ elsif definition[:name] == "snapshot" || definition[:definition][:type] == "snapshot"
189
+ Es::SnapshotField.new("snapshot", "snapshot")
190
+ elsif definition[:name] == "autoincrement"
191
+ Es::AutoincrementField.new("generate", "autoincrement")
192
+ elsif definition[:name] == "duration"
193
+ Es::DurationField.new("duration", "duration")
194
+ elsif definition[:name] == "velocity"
195
+ Es::DurationField.new("velocity", "velocity")
196
+ elsif definition[:definition][:type] == "historicid"
197
+ Es::HIDField.new('hid', "historicid",Es::Helpers.get_historyid_settings(definition[:definition][:ops]))
198
+ elsif definition[:name].downcase == "iswon" || definition[:name].downcase == "isclosed" || definition[:name].downcase == "stagename" || definition[:name].downcase == "daytoclose" || definition[:name] == "dayssincelastactivity"
199
+ Es::Field.new(definition[:name], "attribute")
200
+ else
201
+ puts "WARNING! Transformer has found out field #{definition[:name]} which is not in load script, puting to extract as attribute"
202
+ Es::Field.new("#{definition[:name]}", "attribute")
203
+ end
204
+ end
205
+ parsed_timeframe = parseOldFormat_timeframes(internal[:timeframes])
206
+ entity = Entity.new(entity_name, {
207
+ :fields => fields,
208
+ :file => internal[:file],
209
+ :timeframe => parsed_timeframe || global_timeframe || (fail "Timeframe has to be defined"),
210
+ :timezone => timezone
211
+ })
212
+ entity
213
+ end
214
+ rescue Yajl::ParseError => e
215
+ fail Yajl::ParseError.new("Failed during parsing internal JSON. Error message: " + e.message)
216
+ end
217
+ end
218
+
219
+
220
+ def self.parse_timeframes(timeframe_spec, options={})
221
+ return nil if timeframe_spec.nil?
222
+ if timeframe_spec.is_a?(Array) then
223
+ timeframe_spec.map {|t_spec| Es::Timeframe.parse(t_spec, options)}
224
+ else
225
+ Es::Timeframe.parse(timeframe_spec, options)
226
+ end
227
+ end
228
+
229
+ def self.parseOldFormat_timeframes(timeframe_spec)
230
+ return nil if timeframe_spec.nil?
231
+ return Timeframe.parse("latest") if timeframe_spec == "latest"
232
+ if timeframe_spec.is_a?(Array) then
233
+ timeframe_spec.map {|t_spec| Es::Timeframe.parseOldFormat(t_spec)}
234
+ else
235
+ Es::Timeframe.parseOldFormat(timeframe_spec)
236
+ end
237
+ end
238
+
239
+
240
+ def initialize(entities, options = {})
241
+ @entities = entities
242
+ @timeframe = options[:timeframe]
243
+ @timezone = options[:timezone] || 'UTC'
244
+ end
245
+
246
+ def get_entity(name)
247
+ entities.detect {|e| e.name == name}
248
+ end
249
+
250
+ def to_extract_fragment(pid, options = {})
251
+ entities.map do |entity|
252
+ entity.to_extract_fragment(pid, options)
253
+ end
254
+ end
255
+
256
+ end
257
+
258
+ class Load
259
+ attr_accessor :entities
260
+
261
+ def self.parse(spec)
262
+ Load.new(spec.map do |entity_spec|
263
+ Entity.parse(entity_spec)
264
+ end)
265
+ end
266
+
267
+ def self.parseOldFormat(spec)
268
+ Load.new(spec.map do |entity_spec|
269
+ Entity.parseOldFormat(entity_spec[1])
270
+ end)
271
+ end
272
+
273
+ def initialize(entities)
274
+ @entities = entities
275
+ validate
276
+ end
277
+
278
+ def get_merged_entity_for(name)
279
+ entities_to_merge = entities.find_all {|e| e.name == name}
280
+ fail UnableToMerge.new("There is no entity #{name.bright} in current load object.") if entities_to_merge.empty?
281
+ merged_fields = entities_to_merge.inject([]) {|all, e| all.concat e.fields}
282
+ merged_fields = merged_fields.uniq_by {|obj| [obj.name, obj.type]}
283
+ Entity.new(name, {
284
+ :file => "MERGED",
285
+ :fields => merged_fields
286
+ })
287
+ end
288
+
289
+ def get_entity(name)
290
+ entities.detect {|e| e.name == name}
291
+ end
292
+
293
+ def validate
294
+ names = entities.map {|e| e.name}.uniq
295
+ names.each do |name|
296
+ merged_entity = get_merged_entity_for(name)
297
+ end
298
+ end
299
+
300
+ def to_config
301
+ entities.map {|e| e.to_load_config}
302
+ end
303
+
304
+ def to_config_generator
305
+ entities.map do |e|
306
+ d = ActiveSupport::OrderedHash.new
307
+ d['entity'] = e.to_config_generator[:entity]
308
+ d['file'] = "data/estore-in/#{e.to_config_generator[:file].match(/[^\/]*.csv/)[0]}"
309
+ d['fields'] = e.to_config_generator[:fields]
310
+ d
311
+ end
312
+ end
313
+
314
+ def to_config_file(filename)
315
+ File.open(filename, 'w') do |f|
316
+ f.write(JSON.pretty_generate(to_config))
317
+ end
318
+ end
319
+
320
+ end
321
+
322
+ class Entity
323
+ attr_accessor :name, :fields, :file, :timeframes, :timezone
324
+
325
+ def self.create_deleted_entity(name, options = {})
326
+ compatibility_mode = options[:compatibility_mode]
327
+ deleted_type = compatibility_mode ? "isDeleted" : "attribute"
328
+ file = options[:file]
329
+
330
+ e = Es::Entity.new(name, {
331
+ :file => file,
332
+ :fields => [
333
+ Es::Field.new('Timestamp', 'timestamp'),
334
+ Es::Field.new('Id', 'recordid'),
335
+ Es::Field.new('IsDeleted', deleted_type)
336
+ ]
337
+ })
338
+ end
339
+
340
+ def self.parse(spec)
341
+ entity = Entity.new(spec[:entity], {
342
+ :file => Pathname.new(spec[:file]).expand_path.to_s,
343
+ :fields => spec[:fields] && spec[:fields].map {|field_spec| Field.parse(field_spec)}
344
+ })
345
+ end
346
+
347
+ def self.parseOldFormat(spec)
348
+ entity = Entity.new(spec[:entity], {
349
+ :file => spec[:file],
350
+ :fields => spec[:attributes] && spec[:attributes].map {|field_spec| Field.parse(field_spec)}
351
+ })
352
+ end
353
+
354
+
355
+ def initialize(name, options)
356
+ fail Es::IncorrectSpecificationError.new("Entity name is not specified.") if name.nil?
357
+ fail Es::IncorrectSpecificationError.new("Entity name should be a string.") unless name.is_a?(String)
358
+ fail Es::IncorrectSpecificationError.new("Entity name should not be empty.") if name.strip.empty?
359
+ fail Es::IncorrectSpecificationError.new("File is not specified.") if options[:file].nil?
360
+ fail Es::IncorrectSpecificationError.new("File should be a string.") unless options[:file].is_a?(String)
361
+ fail Es::IncorrectSpecificationError.new("Fields are not specified.") if options[:fields].nil?
362
+ fail Es::IncorrectSpecificationError.new("Entity should contain at least one field.") if options[:fields].empty?
363
+ # fail Es::IncorrectSpecificationError.new("Entity should contain at least one recordid field.") if !options[:fields].any? {|f| f.is_recordid?}
364
+
365
+ @name = name
366
+ @fields = options[:fields]
367
+ @file = options[:file]
368
+ if options[:timeframe] && !options[:timeframe].is_a?(Array)
369
+ @timeframes = [options[:timeframe]]
370
+ else
371
+ @timeframes = options[:timeframe]
372
+ end
373
+ @timezone = options[:timezone] || 'UTC'
374
+ fail Es::IncorrectSpecificationError.new("Entity #{name} should not contain multiple fields with the same name.") if has_multiple_same_fields?
375
+ end
376
+
377
+ def has_multiple_same_fields?
378
+ fields.uniq_by {|s| s.name}.count != fields.count
379
+ end
380
+
381
+ def to_extract_fragment(pid, options = {})
382
+ populates_element = (fields.find {|f| f.is_hid?} || fields.find {|f| f.is_recordid?} || fields.find {|f| f.is_autoincrement?})
383
+ fail "Needs to have at least on ID element. Use Id, HID, autoincrement" if populates_element.nil?
384
+ pretty = options[:pretty].nil? ? true : options[:pretty]
385
+ read_map = [{
386
+ :file => Es::Helpers.web_dav_extract_destination_dir(pid, self) + '/' + Es::Helpers.destination_file(self),
387
+ :populates => populates_element.name,
388
+ :columns => (fields.map do |field|
389
+ field.to_extract_fragment(pid, fields, options)
390
+ end)
391
+ }]
392
+
393
+
394
+ d = ActiveSupport::OrderedHash.new
395
+ d['entity'] = name
396
+ d['timezone'] = timezone
397
+ d['readMap'] = (pretty ? read_map : read_map.to_json)
398
+ d['computedStreams'] = '[{"type":"computed","ops":[]}]'
399
+ d['timeFrames'] = (timeframes.map{|t| t.to_extract_fragment(pid, options)})
400
+
401
+ task = ActiveSupport::OrderedHash.new
402
+ task['readTask'] = d
403
+ task
404
+
405
+ end
406
+
407
+ def to_extract_configuration
408
+ d = ActiveSupport::OrderedHash.new
409
+ d['entity'] = name
410
+ d['file'] = "data/estore-out/#{file.match(/[^\/]*.csv/)[0]}"
411
+ d['fields'] = (fields.map do |field|
412
+ field.to_config_generator_extract
413
+ end)
414
+ d['timeframes'] = (timeframes.map{|t| t.to_config_generator_extract})
415
+ final = ActiveSupport::OrderedHash.new
416
+ final['entities'] = [ d ]
417
+ final
418
+ end
419
+
420
+
421
+ def to_load_fragment(pid)
422
+ {
423
+ :uploadTask => {
424
+ :entity => name,
425
+ :file => Es::Helpers.web_dav_load_destination_dir(pid, self) + '/' + Es::Helpers.destination_file(self),
426
+ :attributes => fields.map {|f| f.to_load_fragment(pid)}
427
+ }
428
+ }
429
+ end
430
+
431
+ def to_load_config
432
+ {
433
+ :entity => name,
434
+ :file => file,
435
+ :fields => fields.map {|f| f.to_load_config}
436
+ }
437
+ end
438
+
439
+ def to_config_generator
440
+ {
441
+ :entity => name,
442
+ :file => file,
443
+ :fields => fields.map {|f| f.to_config_generator}
444
+ }
445
+ end
446
+
447
+
448
+ def to_extract_config
449
+ {
450
+ :timezone => timezone,
451
+ :entities => [{
452
+ :entity => name,
453
+ :file => file,
454
+ :fields => fields.map {|f| f.name}
455
+ }]
456
+ }
457
+ end
458
+
459
+ def to_table
460
+ t = Terminal::Table.new :headings => [name]
461
+ fields.map {|f| t << [f.name]}
462
+ t
463
+ end
464
+
465
+ def has_field?(name)
466
+ !!fields.detect {|f| f.name == name}
467
+ end
468
+
469
+ def get_field(name)
470
+ fields.detect {|f| f.name == name}
471
+ end
472
+
473
+ def add_field(field)
474
+ fail Es::IncorrectSpecificationError.new("There already is a field with name #{field.name} in entity #{name}") if fields.detect {|f| f.name == field.name}
475
+ fields << field
476
+ end
477
+
478
+ def load(pid, es_name)
479
+ begin
480
+ GoodData.connection.upload file, Es::Helpers.load_destination_dir(pid, self)
481
+ data = GoodData.post "/gdc/projects/#{pid}/eventStore/stores/#{es_name}/uploadTasks", to_load_fragment(pid).to_json
482
+ link = data["asyncTask"]["link"]["poll"]
483
+ response = GoodData.get(link, :process => false)
484
+ while response.code != 204
485
+ sleep 5
486
+ GoodData.connection.retryable(:tries => 3, :on => RestClient::InternalServerError) do
487
+ sleep 5
488
+ response = GoodData.get(link, :process => false)
489
+ end
490
+ end
491
+ rescue RestClient::RequestFailed => error
492
+ begin
493
+ doc = Yajl::Parser.parse(error.response, :symbolize_keys => true)
494
+ rescue Yajl::ParseError => e
495
+ puts "Error parsing \"#{error.response}\""
496
+ end
497
+ pp doc
498
+ raise error
499
+ end
500
+ end
501
+
502
+ def extract(pid, es_name)
503
+ begin
504
+ data = GoodData.post "/gdc/projects/#{pid}/eventStore/stores/#{es_name}/readTasks", to_extract_fragment(pid, {:pretty => false}).to_json
505
+ link = data["asyncTask"]["link"]["poll"]
506
+ response = GoodData.get(link, :process => false)
507
+ while response.code != 204
508
+ GoodData.connection.retryable(:tries => 3, :on => RestClient::InternalServerError) do
509
+ sleep 5
510
+ response = GoodData.get(link, :process => false)
511
+ end
512
+ response = GoodData.get(link, :process => false)
513
+ end
514
+ puts "Done downloading"
515
+ web_dav_file = Es::Helpers.extract_destination_dir(pid, self) + '/' + Es::Helpers.destination_file(self)
516
+ puts "Grabbing from web dav"
517
+ GoodData.connection.download web_dav_file, file
518
+ puts "Done"
519
+ rescue RestClient::RequestFailed => error
520
+ begin
521
+ doc = Yajl::Parser.parse(error.response, :symbolize_keys => true)
522
+ rescue Yajl::ParseError => e
523
+ puts "Error parsing \"#{error.response}\""
524
+ end
525
+ pp doc
526
+ raise error
527
+ end
528
+ end
529
+
530
+ def truncate(pid, es_name, timestamp)
531
+ begin
532
+ data = GoodData.post "/gdc/projects/#{pid}/eventStore/stores/#{es_name}/truncateTasks", {
533
+ :truncateTask => {
534
+ :entity => @name,
535
+ :timestamp => timestamp.to_i
536
+ }
537
+ }
538
+ rescue RestClient::BadRequest => error
539
+ puts error.inspect
540
+ raise error
541
+ end
542
+ link = data["asyncTask"]["link"]["poll"]
543
+ response = GoodData.get(link, :process => false)
544
+ while response.code != 204
545
+ sleep 10
546
+ response = GoodData.get(link, :process => false)
547
+ end
548
+ end
549
+
550
+ end
551
+
552
+ # Fields
553
+
554
+ class Field
555
+
556
+ ATTRIBUTE_TYPE = "attribute"
557
+ RECORDID_TYPE = "recordid"
558
+ DATE_TYPE = "date"
559
+ TIME_TYPE = "time"
560
+ FACT_TYPE = "fact"
561
+ TIMESTAMP_TYPE = "timestamp"
562
+ AUTOINCREMENT_TYPE = "autoincrement"
563
+ SNAPSHOT_TYPE = "snapshot"
564
+ HID_TYPE = "hid"
565
+ HISTORIC_TYPE = "historicid"
566
+ DURATION_TYPE = "duration"
567
+ VELOCITY_TYPE = "velocity"
568
+ IS_DELETED_TYPE = "isDeleted"
569
+ TIMEATTRIBUTE_TYPE = "timeAttribute"
570
+
571
+ FIELD_TYPES = [ATTRIBUTE_TYPE, RECORDID_TYPE, DATE_TYPE, TIME_TYPE, FACT_TYPE, TIMESTAMP_TYPE, AUTOINCREMENT_TYPE, SNAPSHOT_TYPE, HID_TYPE, HISTORIC_TYPE, DURATION_TYPE, VELOCITY_TYPE, IS_DELETED_TYPE,TIMEATTRIBUTE_TYPE]
572
+
573
+ def self.parse(spec)
574
+ fail InsufficientSpecificationError.new("Field specification is empty") if spec.nil?
575
+ fail InsufficientSpecificationError.new("Field specification is should be an object") unless spec.is_a?(Hash)
576
+ Field.new(spec[:name], spec[:type])
577
+ end
578
+
579
+ attr_accessor :type, :name
580
+
581
+ def is_recordid?
582
+ type == RECORDID_TYPE
583
+ end
584
+
585
+ def is_timestamp?
586
+ type == TIMESTAMP_TYPE
587
+ end
588
+
589
+ def is_attribute?
590
+ type == ATTRIBUTE_TYPE
591
+ end
592
+
593
+ def is_fact?
594
+ type == FACT_TYPE
595
+ end
596
+
597
+ def is_date?
598
+ type == DATE_TYPE
599
+ end
600
+
601
+ def is_snapshot?
602
+ false
603
+ end
604
+
605
+ def is_duration?
606
+ false
607
+ end
608
+
609
+ def is_autoincrement?
610
+ false
611
+ end
612
+
613
+ def is_hid?
614
+ false
615
+ end
616
+
617
+ def is_velocity?
618
+ false
619
+ end
620
+
621
+ def initialize(name, type)
622
+ fail Es::IncorrectSpecificationError.new("The field name \"#{name.bright}\" does not have type specified. Type should be one of [#{FIELD_TYPES.join(', ')}]") if type.nil?
623
+ fail Es::IncorrectSpecificationError.new("The type of field name \"#{name.bright}\" should be a string.") unless type.is_a?(String)
624
+ fail Es::IncorrectSpecificationError.new("The field name \"#{name.bright}\" does have wrong type specified. Specified \"#{type.bright}\" should be one of [#{FIELD_TYPES.join(', ')}]") unless FIELD_TYPES.include?(type) || type == "none"
625
+ @name = name
626
+ @type = type
627
+ end
628
+
629
+ def to_extract_fragment(pid,fields, options = {})
630
+ {
631
+ :name => name,
632
+ :preferred => name,
633
+ :definition => {
634
+ :ops => [{
635
+ :type => Es::Helpers.type_to_type(type),
636
+ :data => name
637
+ }],
638
+ :type => Es::Helpers.type_to_operation(type)
639
+ }
640
+ }
641
+ end
642
+
643
+ def to_load_fragment(pid)
644
+ {
645
+ :name => name,
646
+ :type => Es::Helpers.type_to_load_type(type)
647
+ }
648
+ end
649
+
650
+ def to_load_config
651
+ {
652
+ :name => name,
653
+ :type => (type == 'none' ? '' : type)
654
+ }
655
+ end
656
+
657
+ def to_config_generator
658
+ d = ActiveSupport::OrderedHash.new
659
+ d['name'] = name
660
+ d['type'] = Es::Helpers.type_to_generator_load_type(type)
661
+ d
662
+ end
663
+
664
+
665
+ def to_config_generator_extract
666
+ name
667
+ end
668
+
669
+ def ==(other)
670
+ other.name == name
671
+ end
672
+
673
+ end
674
+
675
+ class RecordIdField < Field
676
+
677
+ attr_accessor :type, :name, :partial
678
+
679
+ def initialize(name, type, partial)
680
+ fail Es::IncorrectSpecificationError.new("The field name \"#{name.bright}\" does not have type specified. Type should be one of [#{FIELD_TYPES.join(', ')}]") if type.nil?
681
+ fail Es::IncorrectSpecificationError.new("The type of field name \"#{name.bright}\" should be a string.") unless type.is_a?(String)
682
+ fail Es::IncorrectSpecificationError.new("The field name \"#{name.bright}\" does have wrong type specified. Specified \"#{type.bright}\" should be one of [#{FIELD_TYPES.join(', ')}]") unless FIELD_TYPES.include?(type) || type == "none"
683
+ @name = name
684
+ @type = type
685
+ @partial = partial
686
+ end
687
+
688
+ def is_recordid?
689
+ true
690
+ end
691
+
692
+ def to_extract_fragment(pid, fields, options = {})
693
+ if (partial == "true") then
694
+ {
695
+ :name => name,
696
+ :preferred => name,
697
+ :definition => {
698
+ :ops => [{
699
+ :type => Es::Helpers.type_to_type(type),
700
+ :data => name,
701
+ :ops => fields.select{|x| Es::Helpers.type_to_type(x.type) == "stream"}.map do |f|
702
+ {
703
+ :type => "stream",
704
+ :data => f.name
705
+ }
706
+ end
707
+ }],
708
+ :type => Es::Helpers.type_to_operation(type)
709
+ }
710
+ }
711
+ else
712
+ {
713
+ :name => name,
714
+ :preferred => name,
715
+ :definition => {
716
+ :ops => [{
717
+ :type => Es::Helpers.type_to_type(type),
718
+ :data => name
719
+ }],
720
+ :type => Es::Helpers.type_to_operation(type)
721
+ }
722
+ }
723
+ end
724
+ end
725
+ end
726
+
727
+
728
+ class SnapshotField < Field
729
+
730
+ attr_accessor :type, :name
731
+
732
+ def is_snapshot?
733
+ true
734
+ end
735
+
736
+ def to_extract_fragment(pid, fields, options = {})
737
+ {
738
+ :name => name,
739
+ :preferred => name,
740
+ :definition => {
741
+ :type => "snapshot",
742
+ :data => "date"
743
+ }
744
+ }
745
+ end
746
+
747
+ end
748
+
749
+ class HIDField < Field
750
+
751
+ attr_accessor :type, :name, :entity, :fieldsInner, :through
752
+
753
+ def is_hid?
754
+ true
755
+ end
756
+
757
+ def initialize(name, type, options)
758
+ name = "#{name}-#{options[:entity]}"
759
+ super(name, type)
760
+ @entity = options[:entity] || fail("Entity has to be scpecified for a HID Field")
761
+ @fieldsInner = options[:fields] || fail("Fields has to be scpecified for a HID Field")
762
+ @through = options[:through]
763
+ end
764
+
765
+ def to_extract_fragment(pid, fields, options = {})
766
+ {
767
+ :name => name,
768
+ :preferred => name,
769
+ :definition => {
770
+ :ops => [
771
+ through.nil? ? {:type => RECORDID_TYPE} : {:type => "stream", :data => through},
772
+ {
773
+ :type => "entity",
774
+ :data => entity,
775
+ :ops => fieldsInner.map do |f|
776
+ {
777
+ :type => "stream",
778
+ :data => f
779
+ }
780
+ end
781
+ }
782
+ ],
783
+ :type => "historicid"
784
+ }
785
+ }
786
+ end
787
+
788
+
789
+ def to_config_generator_extract
790
+ if through.empty? then
791
+ {
792
+ :hid =>
793
+ {
794
+ :from_entity => entity,
795
+ :from_fields => fields.map{|f| f}
796
+ }
797
+ }
798
+ else
799
+ {
800
+ :hid =>
801
+ {
802
+ :from_entity => entity,
803
+ :from_fields => fields.map{|f| f},
804
+ :connected_through => through
805
+ }
806
+
807
+ }
808
+ end
809
+ end
810
+
811
+
812
+ end
813
+
814
+ class DurationField < Field
815
+
816
+ attr_accessor :type, :name, :attribute, :value, :control_attribute
817
+
818
+ def initialize(name, type, options = {})
819
+ super(name, type)
820
+ @attribute = options[:attribute] || "IsClosed"
821
+ @value = options[:value] || "false"
822
+ @control_attribute = options[:control_attribute] || "StageName"
823
+ end
824
+
825
+ def is_duration?
826
+ true
827
+ end
828
+
829
+ def to_extract_fragment(pid, fields, options = {})
830
+ {
831
+ :name => "StageDuration",
832
+ :preferred => "stageduration",
833
+ :definition => {
834
+ :type => "case",
835
+ :ops => [{
836
+ :type => "option",
837
+ :ops => [{
838
+ :type => "=",
839
+ :ops => [{
840
+ :type => "stream",
841
+ :data => attribute
842
+ },
843
+ {
844
+ :type => "match",
845
+ :data => value
846
+ }]
847
+ },
848
+ {
849
+ :type => "duration",
850
+ :ops => [{
851
+ :type => "stream",
852
+ :data => control_attribute
853
+ }]
854
+ }]
855
+ },
856
+ {
857
+ :type => "option",
858
+ :ops => [{
859
+ :type => "const",
860
+ :data => 1
861
+ },
862
+ {
863
+ :type => "const",
864
+ :data => 0
865
+ }]
866
+ }]
867
+ }
868
+ }
869
+ end
870
+ end
871
+
872
+ class VelocityField < Field
873
+
874
+ attr_accessor :type, :name, :control_attribute
875
+
876
+ def initialize(name, type, options = {})
877
+ super(name, type)
878
+ @control_attribute = options[:control_attribute] || "StageName"
879
+ end
880
+
881
+ def is_velocity?
882
+ true
883
+ end
884
+
885
+ def to_extract_fragment(pid, fields, options = {})
886
+ {
887
+ :name => "StageVelocity",
888
+ :preferred => "stagevelocity",
889
+ :definition => {
890
+ :type => "velocity",
891
+ :ops => [{
892
+ :type => "stream",
893
+ :data => control_attribute
894
+ }]
895
+ }
896
+ }
897
+ end
898
+ end
899
+
900
+
901
+ class AutoincrementField < Field
902
+
903
+ attr_accessor :type, :name
904
+
905
+ def is_autoincrement?
906
+ true
907
+ end
908
+
909
+ def to_extract_fragment(pid, fields, options = {})
910
+ {
911
+ :name => name,
912
+ :preferred => name,
913
+ :definition => {
914
+ :type => "generate",
915
+ :data => "autoincrement"
916
+ }
917
+ }
918
+ end
919
+ end
920
+
921
+ module Helpers
922
+ TEMPLATE_DIR = "./lib/templates"
923
+
924
+ def self.has_more_lines?(path)
925
+ counter = 0
926
+ File.open(path, "r") do |infile|
927
+ while (line = infile.gets)
928
+ counter += 1
929
+ break if counter > 2
930
+ end
931
+ end
932
+ counter > 1
933
+ end
934
+
935
+ def self.load_config(filename, validate=true)
936
+ json = File.new(filename, 'r')
937
+ parser = Yajl::Parser.new(:symbolize_keys => true)
938
+ begin
939
+ doc = parser.parse(json)
940
+ rescue Yajl::ParseError => e
941
+ fail Yajl::ParseError.new("Failed during parsing file #{filename}\n" + e.message)
942
+ end
943
+ end
944
+
945
+ def self.web_dav_load_destination_dir(pid, entity)
946
+ "/uploads/#{pid}"
947
+ end
948
+
949
+ def self.web_dav_extract_destination_dir(pid, entity)
950
+ "/out_#{pid}_#{entity.name}"
951
+ end
952
+
953
+ def self.load_destination_dir(pid, entity)
954
+ "#{pid}"
955
+ end
956
+
957
+ def self.extract_destination_dir(pid, entity)
958
+ "out_#{pid}_#{entity.name}"
959
+ end
960
+
961
+ def self.destination_file(entity, options={})
962
+ with_date = options[:with_date]
963
+ deleted = options[:deleted]
964
+ source = entity.file
965
+ filename = File.basename(source)
966
+ base = File.basename(source, '.*')
967
+ ext = File.extname(filename)
968
+ base = deleted ? "#{base}_deleted" : base
969
+ with_date ? base + '_' + DateTime.now.strftime("%Y-%M-%d_%H:%M:%S") + ext : base + ext
970
+ end
971
+
972
+ def self.type_to_load_type(type)
973
+ types = {
974
+ Es::Field::RECORDID_TYPE => "recordid",
975
+ Es::Field::TIMESTAMP_TYPE => "timestamp",
976
+ Es::Field::ATTRIBUTE_TYPE => "attribute",
977
+ Es::Field::FACT_TYPE => "fact",
978
+ Es::Field::TIME_TYPE => "timeAttribute",
979
+ Es::Field::DATE_TYPE => "timeAttribute",
980
+ Es::Field::IS_DELETED_TYPE => "isDeleted",
981
+ Es::Field::TIMEATTRIBUTE_TYPE => "timeAttribute"
982
+ }
983
+ if types.has_key?(type) then
984
+ types[type]
985
+ else
986
+ fail "Type #{type} not found."
987
+ end
988
+ end
989
+
990
+
991
+ def self.type_to_type(type)
992
+ types = {
993
+ Es::Field::RECORDID_TYPE => "recordid",
994
+ Es::Field::ATTRIBUTE_TYPE => "stream",
995
+ Es::Field::FACT_TYPE => "stream",
996
+ Es::Field::SNAPSHOT_TYPE => "snapshot",
997
+ Es::Field::TIME_TYPE => "stream",
998
+ Es::Field::DATE_TYPE => "stream",
999
+ Es::Field::TIMEATTRIBUTE_TYPE => "stream"
1000
+ }
1001
+ if types.has_key?(type) then
1002
+ types[type]
1003
+ else
1004
+ fail "Type #{type} not found."
1005
+ end
1006
+ end
1007
+
1008
+ def self.type_to_operation(type)
1009
+ types = {
1010
+ Es::Field::RECORDID_TYPE => "value",
1011
+ Es::Field::ATTRIBUTE_TYPE => "value",
1012
+ Es::Field::FACT_TYPE => "number",
1013
+ Es::Field::SNAPSHOT_TYPE => "snapshot",
1014
+ Es::Field::TIME_TYPE => "key",
1015
+ Es::Field::DATE_TYPE => "date",
1016
+ Es::Field::TIMEATTRIBUTE_TYPE => "key"
1017
+ }
1018
+ if types.has_key?(type) then
1019
+ types[type]
1020
+ else
1021
+ fail "Type #{type} not found."
1022
+ end
1023
+ end
1024
+
1025
+ def self.type_to_generator_load_type(type)
1026
+ types = {
1027
+ Es::Field::RECORDID_TYPE => "recordid",
1028
+ Es::Field::TIMESTAMP_TYPE => "timestamp",
1029
+ Es::Field::ATTRIBUTE_TYPE => "attribute",
1030
+ Es::Field::FACT_TYPE => "fact",
1031
+ Es::Field::TIME_TYPE => "time",
1032
+ Es::Field::DATE_TYPE => "date",
1033
+ Es::Field::IS_DELETED_TYPE => "isDeleted",
1034
+ Es::Field::TIMEATTRIBUTE_TYPE => "time"
1035
+ }
1036
+ if types.has_key?(type) then
1037
+ types[type]
1038
+ else
1039
+ fail "Type #{type} not found."
1040
+ end
1041
+ end
1042
+
1043
+
1044
+ def self.get_historyid_settings(json)
1045
+ entity_fields = Array.new
1046
+ entity_name = ""
1047
+ connected_through = ""
1048
+ json.map do |inner_part|
1049
+ if (inner_part[:type] == "entity")
1050
+ entity_name = inner_part[:data]
1051
+ inner_part[:ops].map do |fields|
1052
+ entity_fields << fields[:data]
1053
+ end
1054
+ elsif (inner_part[:type] == "stream")
1055
+ connected_through = inner_part[:data]
1056
+ end
1057
+ end
1058
+ {
1059
+ :entity => entity_name,
1060
+ :fields => entity_fields,
1061
+ :through => connected_through
1062
+ }
1063
+ end
1064
+
1065
+
1066
+ end
1067
+
1068
+ end
1069
+
1070
+ # Hack for 1.8.7
1071
+ # uniq on array does not take block
1072
+ module Enumerable
1073
+ def uniq_by
1074
+ seen = Hash.new { |h,k| h[k] = true; false }
1075
+ reject { |v| seen[yield(v)] }
1076
+ end
1077
+ end