mondrian-rest 0.7.9-java → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbd9d6692bb827f1b325d682e9df7872a83be6a5085b0af4ceb41cc4194b59d7
4
- data.tar.gz: 8e1d250c08c9c88adec227560d816ee79afe1f6c236f0ab31fa5574e981d3d81
3
+ metadata.gz: 2cfb2bb9ca02faf64e851332d271430bed71fcda750ae4f0b46e71cacb8d4c72
4
+ data.tar.gz: 82aa47f20dabcfb4a8deff1e8636f9a8570b667956baf90db522351d6c84a0c7
5
5
  SHA512:
6
- metadata.gz: cc64f9b2911c3e3e7b4142ec91a2796a83190e4abb00a0cb340b0a841d0f706ff43aade8dad0c833e54273b9a4672cf7f2a53223281e4febf9e92fd15bbdaadb
7
- data.tar.gz: 55e965893aec267dd80d1acac550d0b9b85443e36485357468c1c27dcc3e7851205dfef6bc0afcc2b0025786b3a2f07f1448da254cb2c3c826f763724e5c32bb
6
+ metadata.gz: b340ebdde3e89747453ecbbdab83e8c112115a240799778f680cb575359c900ad22b43711ab2f93e402f42dacbd14d6883d612887b11fe103f40e9035b13b60d
7
+ data.tar.gz: a2f4252ee653683f7e39df31f270f052c944b264418c69902c4c7dffec662e6bf3e667936793501cbf6b6e65e64f9278f24e2c956fcf915e2d1c99532fb6df54
data/Rakefile CHANGED
@@ -13,8 +13,8 @@ desc "API Routes"
13
13
  task :routes do
14
14
  require_relative './lib/mondrian_rest.rb'
15
15
  Mondrian::REST::Api.routes.each do |api|
16
- method = api.route_method.ljust(10)
17
- path = api.route_path
16
+ method = api.request_method.ljust(10)
17
+ path = api.path
18
18
  puts " #{method} #{path}"
19
19
  end
20
20
  end
data/lib/mondrian_rest.rb CHANGED
@@ -2,10 +2,10 @@ require 'json'
2
2
 
3
3
  Java::JavaLang::System.setProperty("jdbc.driver.autoload", "true")
4
4
 
5
- require_relative './jars/mondrian-rest_jars.rb'
6
-
7
5
  require 'mondrian-olap'
8
6
  require 'grape'
7
+ require 'active_support'
8
+ require 'active_support/core_ext/enumerable'
9
9
 
10
10
  require_relative './mondrian_rest/nest.rb'
11
11
  require_relative './mondrian_rest/api.rb'
@@ -6,7 +6,6 @@ require_relative './query_helper.rb'
6
6
  require_relative './api_formatters.rb'
7
7
 
8
8
  module Mondrian::REST
9
-
10
9
  class Api < Grape::API
11
10
  version '1', using: :header, vendor: 'mondrian_rest'
12
11
  default_format :json
@@ -20,26 +19,26 @@ module Mondrian::REST
20
19
  end
21
20
 
22
21
  resource :mdx do
23
- content_type :xls, "application/vnd.ms-excel"
22
+ content_type :xls, 'application/vnd.ms-excel'
24
23
  formatter :xls, Mondrian::REST::Formatters::XLS
25
24
 
26
- content_type :csv, "text/csv"
25
+ content_type :csv, 'text/csv'
27
26
  formatter :csv, Mondrian::REST::Formatters::CSV
28
27
 
29
- content_type :json, "application/json"
28
+ content_type :json, 'application/json'
30
29
  formatter :json, Mondrian::REST::Formatters::AggregationJSON
31
30
 
32
- content_type :jsonrecords, "application/x-jsonrecords"
31
+ content_type :jsonrecords, 'application/x-jsonrecords'
33
32
  formatter :jsonrecords, Mondrian::REST::Formatters::JSONRecords
34
33
 
35
- desc "Execute an MDX query against a cube"
36
- content_type :txt, "text/plain"
34
+ desc 'Execute an MDX query against a cube'
35
+ content_type :txt, 'text/plain'
37
36
 
38
37
  params do
39
38
  optional :parents, type: Boolean, desc: "Include members' ancestors"
40
- optional :debug, type: Boolean, desc: "Include generated MDX", default: false
41
- optional :properties, type: Array, desc: "Include member properties"
42
- optional :caption, type: Array, desc: "Replace caption with property", default: []
39
+ optional :debug, type: Boolean, desc: 'Include generated MDX', default: false
40
+ optional :properties, type: Array, desc: 'Include member properties'
41
+ optional :caption, type: Array, desc: 'Replace caption with property', default: []
43
42
  end
44
43
 
45
44
  post do
@@ -52,17 +51,17 @@ module Mondrian::REST
52
51
 
53
52
  resource :flush do
54
53
  params do
55
- requires :secret, type: String, desc: "Secret key"
54
+ requires :secret, type: String, desc: 'Secret key'
56
55
  end
57
- content_type :json, "application/json"
58
- desc "Flush the schema cache"
56
+ content_type :json, 'application/json'
57
+ desc 'Flush the schema cache'
59
58
 
60
59
  get do
61
60
  if ENV['MONDRIAN_REST_SECRET'].nil?
62
- error!("Please set MONDRIAN_REST_SECRET to use this endpoint", 403)
61
+ error!('Please set MONDRIAN_REST_SECRET to use this endpoint', 403)
63
62
  end
64
63
  if params[:secret] != ENV['MONDRIAN_REST_SECRET']
65
- error!("Invalid secret key.", 403)
64
+ error!('Invalid secret key.', 403)
66
65
  end
67
66
  {
68
67
  'status' => olap_flush
@@ -71,7 +70,7 @@ module Mondrian::REST
71
70
  end
72
71
 
73
72
  resource :cubes do
74
- content_type :json, "application/json"
73
+ content_type :json, 'application/json'
75
74
  default_format :json
76
75
  desc "Returns the cubes defined in this server's schema"
77
76
  get do
@@ -81,9 +80,9 @@ module Mondrian::REST
81
80
  end
82
81
 
83
82
  route_param :cube_name do
84
- desc "Return a cube"
83
+ desc 'Return a cube'
85
84
  params do
86
- requires :cube_name, type: String, desc: "Cube name"
85
+ requires :cube_name, type: String, desc: 'Cube name'
87
86
  end
88
87
 
89
88
  get do
@@ -92,7 +91,7 @@ module Mondrian::REST
92
91
  end
93
92
 
94
93
  resource :members do
95
- desc "return a member by its full name"
94
+ desc 'return a member by its full name'
96
95
  params do
97
96
  requires :full_name,
98
97
  type: String,
@@ -106,46 +105,47 @@ module Mondrian::REST
106
105
  if m.nil?
107
106
  error!("Member `#{member_full_name}` not found in cube `#{params[:cube_name]}`", 404)
108
107
  end
109
- m.to_h.merge({
110
- ancestors: m.ancestors.map(&:to_h),
111
- dimension: m.dimension_info
112
- })
108
+ m.to_h.merge(
109
+ ancestors: m.ancestors.map(&:to_h),
110
+ dimension: m.dimension_info
111
+ )
113
112
  end
114
113
  end
115
114
 
116
-
117
115
  resource :aggregate do
118
- content_type :xls, "application/vnd.ms-excel"
116
+ content_type :xls, 'application/vnd.ms-excel'
119
117
  formatter :xls, Mondrian::REST::Formatters::XLS
120
118
 
121
- content_type :csv, "text/csv"
119
+ content_type :csv, 'text/csv'
122
120
  formatter :csv, Mondrian::REST::Formatters::CSV
123
121
 
124
- content_type :json, "application/json"
122
+ content_type :json, 'application/json'
125
123
  formatter :json, Mondrian::REST::Formatters::AggregationJSON
126
124
 
127
- content_type :jsonrecords, "application/x-jsonrecords"
125
+ content_type :jsonrecords, 'application/x-jsonrecords'
128
126
  formatter :jsonrecords, Mondrian::REST::Formatters::JSONRecords
129
127
 
130
- content_type :jsonstat, "application/x-jsonstat"
131
- formatter :jsonstat, Mondrian::REST::Formatters::JSONStat
132
-
133
128
  rescue_from PropertyError do |e|
134
- error!({error: e}, 400)
129
+ error!({ error: e }, 400)
135
130
  end
136
131
 
137
- desc "aggregate from query parameters"
132
+ desc 'aggregate from query parameters'
138
133
  params do
139
134
  optional :measures, type: Array
140
- optional :cut, type: Array, desc: "Specification of slicer axis"
141
- optional :drilldown, type: Array, desc: "Dimension(s) to be drilled down"
142
- optional :nonempty, type: Boolean, desc: "Only return non empty cells"
143
- optional :sparse, type: Boolean, desc: "Skip rows where all measures are null (only applies to CSV, XLS and JSONRECORDS)", default: !java.lang.System.getProperty('mondrian-rest.sparseDefault').nil?
144
- optional :distinct, type: Boolean, desc: "Apply DISTINCT() to every axis"
135
+ optional :cut, type: Array, desc: 'Specification of slicer axis'
136
+ optional :drilldown, type: Array, desc: 'Dimension(s) to be drilled down'
137
+ optional :nonempty, type: Boolean, desc: 'Only return non empty cells'
138
+ optional :sparse, type: Boolean, desc: 'Skip rows where all measures are null (only applies to CSV, XLS and JSONRECORDS)', default: !java.lang.System.getProperty('mondrian-rest.sparseDefault').nil?
139
+ optional :distinct, type: Boolean, desc: 'Apply DISTINCT() to every axis'
145
140
  optional :parents, type: Boolean, desc: "Include members' ancestors"
146
- optional :debug, type: Boolean, desc: "Include generated MDX", default: false
147
- optional :properties, type: Array, desc: "Include member properties"
148
- optional :caption, type: Array, desc: "Replace caption with property", default: []
141
+ optional :debug, type: Boolean, desc: 'Include generated MDX', default: false
142
+ optional :properties, type: Array, desc: 'Include member properties'
143
+ optional :caption, type: Array, desc: 'Replace caption with property', default: []
144
+ optional :filter, type: Array, desc: "Filter by measure value. Accepts: #{Mondrian::REST::QueryHelper::VALID_FILTER_OPS.join(', ')}"
145
+ optional :order, type: String, desc: 'Sort by measure or property name'
146
+ optional :order_desc, type: Boolean, desc: 'Sort direction (true is descending)', default: false
147
+ optional :offset, type: Integer, desc: 'Offset this resultset'
148
+ optional :limit, type: Integer, desc: 'Limit number of results'
149
149
  end
150
150
 
151
151
  get do
@@ -161,8 +161,8 @@ module Mondrian::REST
161
161
  route_param :dimension_name do
162
162
  desc "Return a dimension's members"
163
163
  params do
164
- requires :cube_name, type: String, desc: "Cube name"
165
- requires :dimension_name, type: String, desc: "Dimension name"
164
+ requires :cube_name, type: String, desc: 'Cube name'
165
+ requires :dimension_name, type: String, desc: 'Dimension name'
166
166
  end
167
167
 
168
168
  get do
@@ -171,33 +171,37 @@ module Mondrian::REST
171
171
  dimension.to_h(get_members: true)
172
172
  end
173
173
 
174
+ resource :hierarchies do
175
+ route_param :hierarchy_name do
176
+ resource :levels do
177
+ route_param :level_name do
178
+ resource :members do
179
+ params do
180
+ optional :member_properties, type: Array, default: []
181
+ optional :caption, type: String, desc: 'Replace caption with property', default: nil
182
+ optional :children, type: Boolean, default: false
183
+ end
184
+
185
+ get do
186
+ get_members(params)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+
174
194
  resource :levels do
175
195
  route_param :level_name do
176
196
  resource :members do
177
-
178
197
  params do
179
198
  optional :member_properties, type: Array, default: []
180
- optional :caption, type: String, desc: "Replace caption with property", default: nil
199
+ optional :caption, type: String, desc: 'Replace caption with property', default: nil
181
200
  optional :children, type: Boolean, default: false
182
201
  end
183
202
 
184
203
  get do
185
- cube = get_cube_or_404(params[:cube_name])
186
-
187
- dimension = cube.dimension(params[:dimension_name])
188
- if dimension.nil?
189
- error!("dimension #{params[:dimension_name]} not found in cube #{params[:cube_name]}", 404)
190
- end
191
-
192
- level = dimension.hierarchies[0].level(params[:level_name])
193
- if level.nil?
194
- error!("level #{params[:level_name]} not found in dimension #{params[:dimension_name]}")
195
- end
196
-
197
- level.to_h(member_properties: params[:member_properties],
198
- get_children: params[:children],
199
- member_caption: params[:caption],
200
- get_members: true)
204
+ get_members(params)
201
205
  end
202
206
 
203
207
  route_param :member_key,
@@ -205,7 +209,7 @@ module Mondrian::REST
205
209
  requirements: { member_key: /[A-Za-z0-9\.\-\s%]+/i } do
206
210
 
207
211
  params do
208
- optional :caption, type: String, desc: "Replace caption with property", default: nil
212
+ optional :caption, type: String, desc: 'Replace caption with property', default: nil
209
213
  optional :member_properties, type: Array, default: []
210
214
  optional :children, type: Boolean, default: false
211
215
  end
@@ -215,13 +219,13 @@ module Mondrian::REST
215
219
  dimension = cube.dimension(params[:dimension_name])
216
220
  level = dimension.hierarchies[0].level(params[:level_name])
217
221
 
218
- member = level.members.detect { |m|
222
+ member = level.members.detect do |m|
219
223
  m.property_value('MEMBER_KEY').to_s == params[:member_key]
220
- }
224
+ end
221
225
  error!('member not found', 404) if member.nil?
222
226
  member
223
227
  .to_h(params[:member_properties], params[:caption], params[:children])
224
- .merge({ancestors: member.ancestors.map(&:to_h)})
228
+ .merge(ancestors: member.ancestors.map(&:to_h))
225
229
  end
226
230
  end
227
231
  end
@@ -2,29 +2,27 @@ require_relative './formatters/aggregation_json'
2
2
  require_relative './formatters/csv'
3
3
  require_relative './formatters/excel'
4
4
  require_relative './formatters/jsonrecords'
5
- require_relative './formatters/jsonstat'
6
-
7
5
 
8
6
  module Mondrian::REST::Formatters
9
-
10
7
  ##
11
8
  # Generate 'tidy data' (http://vita.had.co.nz/papers/tidy-data.pdf)
12
9
  # from a result set.
13
10
  def self.tidy(result, options)
14
-
15
11
  cube = result.cube
16
12
 
17
13
  add_parents = options[:add_parents]
18
14
  properties = options[:properties]
19
- sparse = options[:sparse]
20
15
  rs = result.to_h(add_parents, options[:debug])
21
16
 
22
- if rs[:values].empty?
23
- return []
24
- end
17
+ return [] if rs[:values].empty?
25
18
 
26
19
  measures = rs[:axes].first[:members]
27
- dimensions = rs[:axis_dimensions][1..-1]
20
+ dimensions = rs[:axes][1..-1]
21
+
22
+ indexed_members = rs[:axes].map do |ax|
23
+ ax[:members].index_by { |m| m[:key] }
24
+ end
25
+
28
26
  columns = []
29
27
  slices = []
30
28
  level_has_all = []
@@ -33,8 +31,7 @@ module Mondrian::REST::Formatters
33
31
  dimensions.each do |dd|
34
32
  if add_parents
35
33
  hier = cube.dimension(dd[:name])
36
- .hierarchies
37
- .first # TODO: Support other hierarchies
34
+ .hierarchy(dd[:hierarchy])
38
35
 
39
36
  level_has_all << hier.has_all?
40
37
  slices << dd[:level_depth]
@@ -51,71 +48,54 @@ module Mondrian::REST::Formatters
51
48
  end
52
49
 
53
50
  props = Mondrian::REST::APIHelpers.parse_properties(properties, dimensions)
54
- pnames = properties.map { |p|
51
+ pnames = properties.map do |p|
55
52
  org.olap4j.mdx.IdentifierNode.parseIdentifier(p).getSegmentList.last.name
56
- }
53
+ end
57
54
 
58
55
  # append properties and measure columns and yield table header
59
56
  y.yield columns + pnames + pluck(measures, :name)
60
57
 
61
- prod = rs[:axes][1..-1].map { |e|
62
- e[:members].map.with_index { |e_, i| [e_,i] }
63
- }
64
- values = rs[:values]
65
-
66
- prod.shift.product(*prod).each do |cell|
67
- cidxs = cell.map { |c,i| i }.reverse
58
+ rs[:cell_keys].each_with_index do |row, i|
59
+ cm = row.each_with_index.map { |member_key, i| indexed_members[i + 1][member_key] }
60
+ msrs = rs[:values][i]
68
61
 
69
- cm = cell.map(&:first)
70
-
71
- msrs = measures.map.with_index { |m, mi|
72
- (cidxs + [mi]).reduce(values) { |_, idx| _[idx] }
73
- }
74
-
75
- next if sparse && msrs.all?(&:nil?)
76
-
77
- if add_parents
78
- vdim = cm.each.with_index.reduce([]) { |cnames, (member, j)|
79
- member[:ancestors][0...slices[j] - (level_has_all[j] ? 1 : 0)].reverse.each { |ancestor|
62
+ if !add_parents
63
+ y.yield pluck(cm, :key).zip(pluck(cm, :caption)).flatten \
64
+ + get_props(cm, pnames, props, dimensions) \
65
+ + msrs
66
+ else
67
+ vdim = cm.each.with_index.reduce([]) do |cnames, (member, j)|
68
+ member[:ancestors][0...slices[j] - (level_has_all[j] ? 1 : 0)].reverse.each do |ancestor|
80
69
  cnames += [ancestor[:key], ancestor[:caption]]
81
- }
70
+ end
82
71
  cnames += [member[:key], member[:caption]]
83
- }
72
+ end
84
73
 
85
74
  y.yield vdim + get_props(cm, pnames, props, dimensions) + msrs
86
- else
87
- row = pluck(cm, :key)
88
- .zip(pluck(cm, :caption))
89
- .flatten
90
-
91
- y.yield row + get_props(cm, pnames, props, dimensions) + msrs
92
75
  end
93
76
  end
94
77
  end
95
78
  end
96
79
 
97
80
  def self.get_props(cm, pnames, props, dimensions)
98
- pvalues = cm.each.with_index.reduce({}) do |h, (member, ax_i)|
81
+ pvalues = cm.each.with_index.each_with_object({}) do |(member, ax_i), h|
99
82
  dname = dimensions[ax_i][:name]
100
- if props[dname] # are there properties requested for members of this dimension?
101
- mmbr_lvl = dimensions[ax_i][:level]
102
- (props[dname][mmbr_lvl] || []).each { |p|
103
- h[p] = member[:properties][p]
104
- }
105
- if member[:ancestors]
106
- props[dname]
107
- .select { |k, _| k != mmbr_lvl } # levels other than member's own
108
- .each { |l, p|
109
- p.each # get all requested props for this level's ancestor
110
- .with_object(member[:ancestors].find { |anc|
111
- anc[:level_name] == l
112
- }) { |prop, anc|
113
- h[prop] = anc[:properties][prop]
114
- }
115
- }
83
+ next unless props[dname] # are there properties requested for members of this dimension?
84
+ mmbr_lvl = dimensions[ax_i][:level]
85
+ (props[dname][mmbr_lvl] || []).each do |p|
86
+ h[p] = member[:properties][p]
87
+ end
88
+ next unless member[:ancestors]
89
+ props[dname]
90
+ .reject { |k, _| k == mmbr_lvl } # levels other than member's own
91
+ .each do |l, p|
92
+ p.each # get all requested props for this level's ancestor
93
+ .with_object(member[:ancestors].find do |anc|
94
+ anc[:level_name] == l
95
+ end) do |prop, anc|
96
+ h[prop] = anc[:properties][prop]
116
97
  end
117
98
  end
118
- h
119
99
  end # reduce
120
100
  pnames.map { |pn| pvalues[pn] }
121
101
  end
@@ -4,7 +4,6 @@ module Mondrian::REST
4
4
  end
5
5
 
6
6
  module APIHelpers
7
-
8
7
  @@olap = nil
9
8
  @@mdx_parser = nil
10
9
 
@@ -42,44 +41,66 @@ module Mondrian::REST
42
41
 
43
42
  def mdx(query)
44
43
  logger.info("Executing MDX query #{query}")
45
- begin
46
- result = olap.execute query
47
- result.mdx = query if params[:debug]
48
- result.properties = params[:properties]
49
- result.caption_properties = params[:caption]
50
- result.cube = Mondrian::OLAP::Cube.new(olap,
51
- olap.raw_connection.prepareOlapStatement(query).getCube)
52
- return result
53
- rescue Mondrian::OLAP::Error => st
54
- error!({error: st.backtrace}, 400)
55
- end
44
+
45
+ result = olap.execute query
46
+ result.mdx = query if params[:debug]
47
+ result.properties = params[:properties]
48
+ result.caption_properties = params[:caption]
49
+ result.cube = Mondrian::OLAP::Cube.new(olap,
50
+ olap.raw_connection.prepareOlapStatement(query).getCube)
51
+ result
52
+ rescue Mondrian::OLAP::Error => st
53
+ error!({ error: st.backtrace }, 400)
56
54
  end
57
55
 
58
56
  def run_from_params(params)
59
57
  cube = get_cube_or_404(params[:cube_name])
60
58
  query = build_query(cube, params)
61
- mdx_query = query.to_mdx
62
59
 
63
60
  result = mdx(query.to_mdx)
64
61
  result.cube = cube
65
62
  result
66
63
  end
67
64
 
65
+ def get_members(params)
66
+ cube = get_cube_or_404(params[:cube_name])
67
+
68
+ dimension = cube.dimension(params[:dimension_name])
69
+ if dimension.nil?
70
+ error!("dimension #{params[:dimension_name]} not found in cube #{params[:cube_name]}", 404)
71
+ end
72
+
73
+ hier = unless params[:hierarchy_name].nil?
74
+ h = dimension.hierarchy(params[:hierarchy_name])
75
+ error!("Hierarchy #{params[:hierarchy_name]} does not exist in dimension #{params[:dimension_name]}", 404) if h.nil?
76
+ h
77
+ else
78
+ dimension.hierarchies.first
79
+ end
80
+
81
+ level = hier.level(params[:level_name])
82
+ if level.nil?
83
+ error!("level #{params[:level_name]} not found in dimension #{params[:dimension_name]}")
84
+ end
85
+
86
+ level.to_h(member_properties: params[:member_properties],
87
+ get_children: params[:children],
88
+ member_caption: params[:caption],
89
+ get_members: true)
90
+ end
91
+
68
92
  NEST = Mondrian::REST::Nest.new
69
- .key { |d| d[0] }
70
- .key { |d| d[1] }
93
+ .key { |d| d[0] }
94
+ .key { |d| d[1] }.freeze
71
95
 
72
96
  def self.parse_caption_properties(cprops)
73
- if cprops.nil? or cprops.size < 1
74
- return {}
75
- end
97
+ return {} if cprops.nil? || cprops.empty?
76
98
 
77
- NEST.map(cprops.map { |cp|
99
+ NEST.map(cprops.map do |cp|
78
100
  names = org.olap4j.mdx.IdentifierNode.parseIdentifier(cp).getSegmentList.to_a.map(&:name)
79
101
  # IF prop in Dim.Hier.Lvl.Prop format, skip names[1]
80
102
  names.size == 4 ? [names[0], names[2], names[3]] : names
81
- })
82
-
103
+ end)
83
104
  end
84
105
 
85
106
  ##
@@ -9,6 +9,29 @@ module Mondrian
9
9
  raw_cube.getSets
10
10
  end
11
11
 
12
+ def measure(name)
13
+ self.dimension('Measures')
14
+ .hierarchy
15
+ .levels
16
+ .first
17
+ .members
18
+ .detect { |m| m.name == name }
19
+ end
20
+
21
+ def valid_measure?(name)
22
+ !self.measure(name).nil?
23
+ end
24
+
25
+ def level(*parts)
26
+ dim = self.dimension(parts[0])
27
+ hier = if parts.size > 2
28
+ dim.hierarchy(parts[1])
29
+ else
30
+ dim.hierarchies[0]
31
+ end
32
+ hier.level(parts.last)
33
+ end
34
+
12
35
  def to_h
13
36
  # gather named sets
14
37
  named_sets = self.named_sets
@@ -128,6 +151,10 @@ module Mondrian
128
151
  }
129
152
  end
130
153
 
154
+ def property(name)
155
+ self.raw_level.getProperties.asMap[name]
156
+ end
157
+
131
158
  end
132
159
 
133
160
  class Member
@@ -171,13 +198,15 @@ module Mondrian
171
198
  def dimension_info
172
199
  d = @raw_member.getDimension()
173
200
  l = @raw_member.getLevel()
201
+ h = l.getHierarchy()
174
202
 
175
203
  x = {
176
- :name => d.getName,
177
- :caption => d.getCaption,
178
- :type => self.dimension_type,
179
- :level => l.getCaption,
180
- :level_depth => l.depth
204
+ name: d.getName,
205
+ caption: d.getCaption,
206
+ type: self.dimension_type,
207
+ level: l.getCaption,
208
+ level_depth: l.depth,
209
+ hierarchy: h.getName
181
210
  }
182
211
  end
183
212
 
@@ -201,49 +230,60 @@ module Mondrian
201
230
  # return the contents of the filter axis
202
231
  # puts self.raw_cell_set.getFilterAxis.inspect
203
232
 
204
- dimensions = self.axis_members.map { |am| am.first ? am.first.dimension_info : nil }
233
+ drilldowns_num = if self.raw_cell_set.getMetaData.getAxesMetaData.size == 1
234
+ 0
235
+ else
236
+ self.raw_cell_set.getMetaData.getAxesMetaData[1].getHierarchies.size
237
+ end
238
+ dimensions = self.axis_members
239
+ .flatten
240
+ .map(&:dimension_info)
241
+ .uniq
242
+
243
+ pprops = {}
244
+ pprops = Mondrian::REST::APIHelpers.parse_properties(self.properties, dimensions[1..-1]) unless self.properties.nil?
245
+ cprops = Mondrian::REST::APIHelpers.parse_caption_properties(self.caption_properties)
246
+
247
+ measure_axis = { members: self.axis_members.first.flatten.map(&:to_h) }
248
+ measure_axis.merge!(dimensions[0]) if dimensions.size > 0
249
+
250
+ member_axes = if drilldowns_num > 1
251
+ self.axis_members[1].transpose
252
+ elsif drilldowns_num == 1
253
+ [self.axis_members[1]]
254
+ else
255
+ []
256
+ end
205
257
 
206
- pprops = unless self.properties.nil?
207
- Mondrian::REST::APIHelpers.parse_properties(self.properties,
208
- dimensions[1..-1]) # exclude Measures dimension
209
- else
210
- {}
211
- end
212
-
213
- cprops = Mondrian::REST::APIHelpers.parse_caption_properties(
214
- self.caption_properties
215
- )
216
-
217
- rv = {
218
- axes: self.axis_members.each_with_index.map { |a, i|
258
+ {
259
+ axes: [measure_axis] + member_axes.each_with_index.map { |a, axis_index|
219
260
  {
220
- members: a.map { |m|
261
+ members: a.uniq(&:full_name).map { |m|
221
262
  mh = m.to_h(
222
263
  pprops.dig(m.raw_member.getDimension.name, m.raw_level.name) || [],
223
264
  (cprops.dig(m.raw_member.getDimension.name, m.raw_level.name) || [[]])[0][-1]
224
265
  )
225
266
  if parents
226
- mh.merge!({
227
- ancestors: m.ancestors.map { |ma|
228
- ma.to_h(
229
- pprops.dig(ma.raw_member.getDimension.name, ma.raw_level.name) || [],
230
- (cprops.dig(ma.raw_member.getDimension.name, ma.raw_level.name) || [[]])[0][-1]
231
- )
232
- }
233
- })
267
+ mh[:ancestors] = m.ancestors.map { |ma|
268
+ ma.to_h(
269
+ pprops.dig(ma.raw_member.getDimension.name, ma.raw_level.name) || [],
270
+ (cprops.dig(ma.raw_member.getDimension.name, ma.raw_level.name) || [[]])[0][-1])
271
+ }
234
272
  end
235
273
  mh
236
274
  }
237
- }
275
+ }.merge(dimensions.size > 1 ? dimensions[axis_index+1] : {})
238
276
  },
239
- axis_dimensions: dimensions,
240
- values: self.values
277
+ cell_keys: if drilldowns_num > 1
278
+ self.axis_members[1].map { |t| t.map { |m| m.property_value('MEMBER_KEY') } }
279
+ elsif drilldowns_num == 1
280
+ self.axis_members[1].map { |t| [ t.property_value('MEMBER_KEY') ] }
281
+ else
282
+ []
283
+ end,
284
+ values: self.values,
285
+ mdx: debug ? self.mdx : nil
241
286
  }
242
-
243
- rv[:mdx] = self.mdx if debug
244
-
245
- rv
246
-
247
287
  end
248
288
  end
249
289
  end
@@ -1,6 +1,11 @@
1
1
  # coding: utf-8
2
2
  module Mondrian::REST
3
3
  module QueryHelper
4
+ VALID_FILTER_OPS = [
5
+ '>', '<', '>=', '<=', '=', '<>'
6
+ ].freeze
7
+ VALID_FILTER_RE = /(?<measure>[a-zA-Z0-9\s]+)\s*(?<operand>#{VALID_FILTER_OPS.join("|")})\s*(?<value>-?\d+\.?\d*)/
8
+ MEMBER_METHODS = %w[Caption Key Name UniqueName].freeze
4
9
 
5
10
  def unparse_node(node)
6
11
  sw = java.io.StringWriter.new
@@ -11,18 +16,16 @@ module Mondrian::REST
11
16
 
12
17
  def get_dimension(cube, dname)
13
18
  cube_dimensions = cube.dimensions
14
- .find_all { |d| d.dimension_type != :measures }
19
+ .find_all { |d| d.dimension_type != :measures }
15
20
  dim = cube_dimensions.find { |d| d.name == dname }
16
21
  error!("Dimension #{dname} does not exist", 400) if dim.nil?
17
22
  dim
18
23
  end
19
24
 
20
25
  def get_member(cube, member_exp)
21
- begin
22
- return cube.member(member_exp)
23
- rescue Java::JavaLang::IllegalArgumentException
24
- error!("Illegal expression: #{member_exp}", 400)
25
- end
26
+ cube.member(member_exp)
27
+ rescue Java::JavaLang::IllegalArgumentException
28
+ error!("Illegal expression: #{member_exp}", 400)
26
29
  end
27
30
 
28
31
  def get_named_set(cube, named_set_exp)
@@ -39,48 +42,42 @@ module Mondrian::REST
39
42
  case p
40
43
  when org.olap4j.mdx.CallNode
41
44
  case p.getOperatorName
42
- when "{}"
45
+ when '{}'
43
46
  # check that the set contains only Members of a single dimension level
44
- set_members = p.getArgList.map { |id_node|
47
+ set_members = p.getArgList.map do |id_node|
45
48
  get_member(cube, unparse_node(id_node))
46
- }
49
+ end
47
50
 
48
- if set_members.any? { |m| m.nil? }
49
- error!("Illegal cut. Unknown member in cut set", 400)
51
+ if set_members.any?(&:nil?)
52
+ error!('Illegal cut. Unknown member in cut set', 400)
50
53
  end
51
54
 
52
55
  ls = set_members.map(&:raw_level).uniq
53
- unless ls.size == 1
54
- error!("Illegal cut: " + cut_expr, 400)
55
- end
56
+ error!('Illegal cut: ' + cut_expr, 400) unless ls.size == 1
56
57
  { level: ls.first, cut: unparse_node(p), type: :set, set_members: set_members }
57
- when "()"
58
+ when '()'
58
59
  # check that the range contains a valid range
59
60
 
60
61
  unless p.getArgList.first.is_a?(org.olap4j.mdx.CallNode) \
61
- and p.getArgList.first.getOperatorName == ':'
62
- error!("Illegal cut: " + cut_expr, 400)
62
+ && (p.getArgList.first.getOperatorName == ':')
63
+ error!('Illegal cut: ' + cut_expr, 400)
63
64
  end
64
65
 
65
- ls = p.getArgList.first.getArgList.map { |id_node|
66
+ ls = p.getArgList.first.getArgList.map do |id_node|
66
67
  get_member(cube, unparse_node(id_node)).raw_level
67
- }.uniq
68
+ end.uniq
68
69
 
69
- unless ls.size == 1
70
- error!("Illegal cut: " + cut_expr, 400)
71
- end
70
+ error!('Illegal cut: ' + cut_expr, 400) unless ls.size == 1
72
71
 
73
72
  { level: ls.first, cut: unparse_node(p), type: :range }
74
73
  else
75
- error!("Illegal cut: " + cut_expr, 400)
74
+ error!('Illegal cut: ' + cut_expr, 400)
76
75
  end
77
76
  when org.olap4j.mdx.IdentifierNode
78
77
 
79
78
  # does cut_expr look like a NamedSet?
80
79
  s = get_named_set(cube, cut_expr)
81
- if !s.nil?
82
- return { level: nil, cut: cut_expr, type: :named_set }
83
- end
80
+ return { level: nil, cut: cut_expr, type: :named_set } unless s.nil?
84
81
 
85
82
  # if `cut_expr` looks like a member, check that it's level is
86
83
  # equal to `level`
@@ -92,7 +89,7 @@ module Mondrian::REST
92
89
 
93
90
  { level: m.raw_level, cut: cut_expr, type: :member }
94
91
  else
95
- error!("Illegal cut: " + cut_expr, 400)
92
+ error!('Illegal cut: ' + cut_expr, 400)
96
93
  end
97
94
  end
98
95
 
@@ -100,10 +97,9 @@ module Mondrian::REST
100
97
  # Parses a drilldown specification
101
98
  # XXX TODO write doc
102
99
  def parse_drilldown(cube, drilldown)
103
-
104
100
  # check if the drilldown is a named set
105
101
  named_sets = cube.named_sets
106
- if ns = named_sets.find { |ns| ns.name == drilldown }
102
+ if (ns = named_sets.find { |ns| ns.name == drilldown })
107
103
  return ns
108
104
  end
109
105
 
@@ -138,46 +134,101 @@ module Mondrian::REST
138
134
  level
139
135
  end
140
136
 
141
- def build_query(cube, options={})
137
+ def parse_measure_filter(cube, filter)
138
+ m = VALID_FILTER_RE.match(filter)
139
+ error!("Filter clause #{filter} is invalid", 400) if m.nil?
140
+
141
+ unless cube.valid_measure?(m['measure'].strip)
142
+ error!("Invalid filter: measure #{m['measure'].strip} does not exist", 400)
143
+ end
144
+
145
+ {
146
+ measure: m['measure'].strip,
147
+ operand: m['operand'].strip,
148
+ value: m['value'].strip
149
+ }
150
+ end
151
+
152
+ def parse_order(cube, order, order_desc)
153
+ begin
154
+ s = org.olap4j.mdx.IdentifierNode.parseIdentifier(order).getSegmentList.map(&:getName)
155
+ rescue Java::JavaLang::IllegalArgumentException
156
+ error!("Invalid order specification: #{order}", 400)
157
+ end
158
+
159
+ if s[0] == 'Measures'
160
+ error!("Invalid measure in order: #{s[1]}", 400) unless cube.valid_measure?(s[1])
161
+
162
+ return {
163
+ order: cube.measure(s[1]).full_name,
164
+ desc: order_desc
165
+ }
166
+ else # ordering by a property
167
+ # we need at least dim.level.property
168
+ error!('Invalid order: specify at least [Dimension].[Level].[Property]', 400) if s.size < 3
169
+
170
+ lvl = cube.level(*s[0..-2])
171
+ error!("Invalid order: level #{s[0..-2].join('.')} not found", 400) if lvl.nil?
172
+
173
+ last = if MEMBER_METHODS.include?(s.last)
174
+ s.last
175
+ else
176
+ prop = lvl.property(s[-1])
177
+ error!("Invalid order: property #{order} not found", 400) if prop.nil?
178
+ "Properties('#{s.last}')"
179
+ end
180
+
181
+ return {
182
+ order: s[0..-2].map do |n|
183
+ Java::MondrianOlap::Util.quoteMdxIdentifier(n)
184
+ end.join('.') + '.CurrentMember.' + last,
185
+ desc: order_desc
186
+ }
187
+ end
188
+ end
142
189
 
190
+ def build_query(cube, options = {})
143
191
  measure_members = cube.dimension('Measures').hierarchy.levels.first.members
144
192
  options = {
145
193
  'cut' => [],
146
194
  'drilldown' => [],
147
195
  'measures' => [measure_members.first.name],
148
196
  'nonempty' => false,
149
- 'distinct' => false
197
+ 'distinct' => false,
198
+ 'filter' => [],
199
+ 'order' => nil,
200
+ 'order_desc' => false,
201
+ 'offset' => nil,
202
+ 'limit' => nil
150
203
  }.merge(options)
151
204
 
152
205
  # validate measures exist
153
206
  cm_names = measure_members.map(&:name)
154
207
 
155
- options['measures'].each { |m|
208
+ options['measures'].each do |m|
156
209
  error!("Measure #{m} does not exist in cube #{cube.name}", 400) unless cm_names.include?(m)
157
- }
210
+ end
211
+
212
+ filters = options['filter'].map { |f| parse_measure_filter(cube, f) }
158
213
 
159
214
  # measures go in axis(0) of the resultset
160
215
  query = olap.from(cube.name)
161
- .axis(0,
162
- *options['measures'].map { |m|
163
- measure_members.find { |cm| cm.name == m }.full_name
164
- })
165
- if options['nonempty']
166
- query = query.nonempty
167
- end
168
- axis_idx = 1
216
+ .axis(0,
217
+ *options['measures'].map do |m|
218
+ measure_members.find { |cm| cm.name == m }.full_name
219
+ end)
220
+ query = query.nonempty if options['nonempty']
169
221
 
170
222
  query_axes = options['drilldown'].map { |dd| parse_drilldown(cube, dd) }
171
223
 
172
- slicer_axis = options['cut'].reduce({}) { |h, cut_expr|
224
+ slicer_axis = options['cut'].each_with_object({}) do |cut_expr, h|
173
225
  pc = parse_cut(cube, cut_expr)
174
226
  h[pc[:level]] = pc
175
- h
176
- }
227
+ end
177
228
 
178
229
  dd = query_axes.map do |qa|
179
230
  # if drilling down on a named set
180
- if qa.kind_of?(Java::MondrianOlap4j::MondrianOlap4jNamedSet)
231
+ if qa.is_a?(Java::MondrianOlap4j::MondrianOlap4jNamedSet)
181
232
  "[#{qa.name}]"
182
233
  # there's a slice (cut) on this axis
183
234
  elsif slicer_axis[qa.raw_level]
@@ -188,10 +239,10 @@ module Mondrian::REST
188
239
  else
189
240
  cut[:cut]
190
241
  end
191
- elsif cut = slicer_axis.find { |lvl, cut|
242
+ elsif cut = slicer_axis.find do |lvl, cut|
192
243
  next if cut[:type] == :named_set
193
244
  qa.raw_level.hierarchy == lvl.hierarchy && lvl.depth < qa.depth
194
- }
245
+ end
195
246
  slicer_axis.delete(cut[0])
196
247
  cut = cut[1]
197
248
 
@@ -200,44 +251,58 @@ module Mondrian::REST
200
251
  "DESCENDANTS(#{cut[:cut]}, #{qa.unique_name})"
201
252
  when :set
202
253
  # TODO
203
- "{" + cut[:set_members].map { |m|
254
+ '{' + cut[:set_members].map do |m|
204
255
  "DESCENDANTS(#{m.full_name}, #{qa.unique_name})"
205
- }.join(",") + "}"
256
+ end.join(',') + '}'
206
257
  when :range
207
258
  # TODO
208
- raise "Unsupported operation"
259
+ raise 'Unsupported operation'
209
260
  end
210
261
  else
211
262
  qa.unique_name + '.Members'
212
263
  end
213
264
  end
214
265
 
215
- # query axes (drilldown)
216
- dd.each do |ds|
217
- query = query.axis(axis_idx,
218
- ds)
266
+ unless dd.empty?
267
+ # Cross join all the drilldowns
268
+ axis_exp = dd.join(' * ')
219
269
 
220
- if options['distinct']
221
- query = query.distinct
270
+ # Apply filters
271
+ unless filters.empty?
272
+ filter_exp = filters.map { |f| "[Measures].[#{org.olap4j.mdx.MdxUtil.mdxEncodeString(f[:measure])}] #{f[:operand]} #{f[:value]}" }.join(' AND ')
273
+ axis_exp = "FILTER(#{axis_exp}, #{filter_exp})"
222
274
  end
223
275
 
224
- if options['nonempty']
225
- query = query.nonempty
276
+ unless options['order'].nil?
277
+ order = parse_order(cube, options['order'], options['order_desc'])
278
+ axis_exp = "ORDER(#{axis_exp}, #{order[:order]}, #{order[:desc] ? 'BDESC' : 'BASC'})"
226
279
  end
227
280
 
228
- axis_idx += 1
281
+ # TODO: Apply pagination
282
+ unless options['offset'].nil?
283
+ axis_exp = if options['limit'].nil?
284
+ "SUBSET(#{axis_exp}, #{options['offset']})"
285
+ else
286
+ "SUBSET(#{axis_exp}, #{options['offset']}, #{options['limit']})"
287
+ end
288
+ end
289
+
290
+ query = query.axis(1, axis_exp)
229
291
  end
230
292
 
293
+ query = query.distinct if options['distinct']
294
+
295
+ query = query.nonempty if options['nonempty']
296
+
231
297
  # slicer axes (cut)
232
298
  if slicer_axis.size >= 1
233
- puts slicer_axis.inspect
234
- query = query.where(slicer_axis.values.map { |v|
299
+ query = query.where(slicer_axis.values.map do |v|
235
300
  if v[:type] == :named_set
236
301
  "[#{v[:cut]}]"
237
302
  else
238
303
  v[:cut]
239
304
  end
240
- }.join(' * '))
305
+ end.join(' * '))
241
306
  end
242
307
  query
243
308
  end
@@ -1,5 +1,5 @@
1
1
  module Mondrian
2
2
  module REST
3
- VERSION = "0.7.9"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -16,11 +16,11 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n").reject { |f| f =~ /^spec\// }
17
17
  s.require_paths = ["lib", "lib/jars"]
18
18
 
19
- s.requirements << 'jar no.ssb.jsonstat:json-stat-java, 0.2.2'
20
19
 
21
20
  s.add_runtime_dependency 'mondrian-olap', ["~> 0.8.0"]
22
21
  s.add_runtime_dependency 'grape', '~> 1.0', '>= 1.0.0'
23
22
  s.add_runtime_dependency 'writeexcel', '~> 1.0', '>= 1.0.5'
23
+ s.add_runtime_dependency 'activesupport', '~> 5.1', '>= 5.1.5'
24
24
 
25
25
  s.add_development_dependency "jar-dependencies", "~> 0.3.2"
26
26
  s.add_development_dependency 'rake', '~> 12.1', '>= 12.1.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mondrian-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 1.0.0
5
5
  platform: java
6
6
  authors:
7
7
  - Manuel Aristarán
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-28 00:00:00.000000000 Z
11
+ date: 2018-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -64,6 +64,26 @@ dependencies:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
66
  version: 1.0.5
67
+ - !ruby/object:Gem::Dependency
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '5.1'
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 5.1.5
76
+ name: activesupport
77
+ prerelease: false
78
+ type: :runtime
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '5.1'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 5.1.5
67
87
  - !ruby/object:Gem::Dependency
68
88
  requirement: !ruby/object:Gem::Requirement
69
89
  requirements:
@@ -205,17 +225,6 @@ files:
205
225
  - LICENSE
206
226
  - README.md
207
227
  - Rakefile
208
- - lib/jars/com/codepoetics/protonpack/1.9/protonpack-1.9.jar
209
- - lib/jars/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar
210
- - lib/jars/com/fasterxml/jackson/core/jackson-core/2.8.5/jackson-core-2.8.5.jar
211
- - lib/jars/com/fasterxml/jackson/core/jackson-databind/2.8.5/jackson-databind-2.8.5.jar
212
- - lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-guava/2.8.5/jackson-datatype-guava-2.8.5.jar
213
- - lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.8.5/jackson-datatype-jdk8-2.8.5.jar
214
- - lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.8.5/jackson-datatype-jsr310-2.8.5.jar
215
- - lib/jars/com/google/guava/guava/19.0/guava-19.0.jar
216
- - lib/jars/me/yanaga/guava-stream/1.0/guava-stream-1.0.jar
217
- - lib/jars/mondrian-rest_jars.rb
218
- - lib/jars/no/ssb/jsonstat/json-stat-java/0.2.2/json-stat-java-0.2.2.jar
219
228
  - lib/mondrian_rest.rb
220
229
  - lib/mondrian_rest/api.rb
221
230
  - lib/mondrian_rest/api_formatters.rb
@@ -224,7 +233,6 @@ files:
224
233
  - lib/mondrian_rest/formatters/csv.rb
225
234
  - lib/mondrian_rest/formatters/excel.rb
226
235
  - lib/mondrian_rest/formatters/jsonrecords.rb
227
- - lib/mondrian_rest/formatters/jsonstat.rb
228
236
  - lib/mondrian_rest/mondrian_ext.rb
229
237
  - lib/mondrian_rest/nest.rb
230
238
  - lib/mondrian_rest/query_helper.rb
@@ -249,8 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
249
257
  - - ">="
250
258
  - !ruby/object:Gem::Version
251
259
  version: '0'
252
- requirements:
253
- - jar no.ssb.jsonstat:json-stat-java, 0.2.2
260
+ requirements: []
254
261
  rubyforge_project:
255
262
  rubygems_version: 2.6.13
256
263
  signing_key:
@@ -1,28 +0,0 @@
1
- # this is a generated file, to avoid over-writing it just delete this comment
2
- begin
3
- require 'jar_dependencies'
4
- rescue LoadError
5
- require 'com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.8.5/jackson-datatype-jdk8-2.8.5.jar'
6
- require 'com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar'
7
- require 'me/yanaga/guava-stream/1.0/guava-stream-1.0.jar'
8
- require 'com/fasterxml/jackson/core/jackson-databind/2.8.5/jackson-databind-2.8.5.jar'
9
- require 'no/ssb/jsonstat/json-stat-java/0.2.2/json-stat-java-0.2.2.jar'
10
- require 'com/fasterxml/jackson/core/jackson-core/2.8.5/jackson-core-2.8.5.jar'
11
- require 'com/fasterxml/jackson/datatype/jackson-datatype-guava/2.8.5/jackson-datatype-guava-2.8.5.jar'
12
- require 'com/google/guava/guava/19.0/guava-19.0.jar'
13
- require 'com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.8.5/jackson-datatype-jsr310-2.8.5.jar'
14
- require 'com/codepoetics/protonpack/1.9/protonpack-1.9.jar'
15
- end
16
-
17
- if defined? Jars
18
- require_jar( 'com.fasterxml.jackson.datatype', 'jackson-datatype-jdk8', '2.8.5' )
19
- require_jar( 'com.fasterxml.jackson.core', 'jackson-annotations', '2.8.0' )
20
- require_jar( 'me.yanaga', 'guava-stream', '1.0' )
21
- require_jar( 'com.fasterxml.jackson.core', 'jackson-databind', '2.8.5' )
22
- require_jar( 'no.ssb.jsonstat', 'json-stat-java', '0.2.2' )
23
- require_jar( 'com.fasterxml.jackson.core', 'jackson-core', '2.8.5' )
24
- require_jar( 'com.fasterxml.jackson.datatype', 'jackson-datatype-guava', '2.8.5' )
25
- require_jar( 'com.google.guava', 'guava', '19.0' )
26
- require_jar( 'com.fasterxml.jackson.datatype', 'jackson-datatype-jsr310', '2.8.5' )
27
- require_jar( 'com.codepoetics', 'protonpack', '1.9' )
28
- end
@@ -1,50 +0,0 @@
1
- require 'java'
2
-
3
- module Mondrian::REST::Formatters
4
- module JSONStat
5
-
6
- def self.call(result, env)
7
-
8
- mapper = Java::ComFasterxmlJacksonDatabind::ObjectMapper.new
9
- mapper.registerModule(Java::NoSsbJsonstat::JsonStatModule.new)
10
- mapper.registerModule(Java::ComFasterxmlJacksonDatatypeJdk8::Jdk8Module.new.configureAbsentsAsNulls(true))
11
- mapper.setSerializationInclusion(Java::ComFasterxmlJacksonAnnotation::JsonInclude::Include::NON_NULL)
12
- mapper.registerModule(Java::ComFasterxmlJacksonDatatypeGuava::GuavaModule.new.configureAbsentsAsNulls(false))
13
-
14
- rs = result.to_h
15
- rdims = rs[:axis_dimensions].reverse
16
-
17
- builder = Java::NoSsbJsonstatV2::Dataset
18
- .create
19
- .withLabel('Aggregation: ')
20
- .withSource(env['REQUEST_URI'] || '')
21
-
22
- dimensions = rdims.map.with_index do |d, i|
23
- dim = Java::NoSsbJsonstatV2::Dimension
24
- .create(d[:name])
25
-
26
- if d[:type] == :measures
27
- dim = dim.withMetricRole
28
- elsif d[:type] == :time
29
- dim = dim.withTimeRole
30
- end
31
-
32
- dim.withLabel(d[:caption])
33
- .withIndexedLabels(
34
- Java::ComGoogleCommonCollect::ImmutableMap.copyOf(
35
- Hash[*(rs[:axes][-1 - i][:members].map { |m| [ m[:key].to_s, m[:caption] ] }.flatten)].to_java
36
- )
37
- )
38
- end
39
-
40
- dataset = builder
41
- .withDimensions(dimensions)
42
- .withValues(rs[:values].flatten)
43
- .build
44
-
45
- mapper.writeValueAsString(dataset)
46
-
47
- end
48
-
49
- end
50
- end