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 +4 -4
- data/Rakefile +2 -2
- data/lib/mondrian_rest.rb +2 -2
- data/lib/mondrian_rest/api.rb +69 -65
- data/lib/mondrian_rest/api_formatters.rb +37 -57
- data/lib/mondrian_rest/api_helpers.rb +42 -21
- data/lib/mondrian_rest/mondrian_ext.rb +76 -36
- data/lib/mondrian_rest/query_helper.rb +127 -62
- data/lib/mondrian_rest/version.rb +1 -1
- data/mondrian-rest.gemspec +1 -1
- metadata +23 -16
- data/lib/jars/com/codepoetics/protonpack/1.9/protonpack-1.9.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/core/jackson-core/2.8.5/jackson-core-2.8.5.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/core/jackson-databind/2.8.5/jackson-databind-2.8.5.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-guava/2.8.5/jackson-datatype-guava-2.8.5.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.8.5/jackson-datatype-jdk8-2.8.5.jar +0 -0
- data/lib/jars/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.8.5/jackson-datatype-jsr310-2.8.5.jar +0 -0
- data/lib/jars/com/google/guava/guava/19.0/guava-19.0.jar +0 -0
- data/lib/jars/me/yanaga/guava-stream/1.0/guava-stream-1.0.jar +0 -0
- data/lib/jars/mondrian-rest_jars.rb +0 -28
- data/lib/jars/no/ssb/jsonstat/json-stat-java/0.2.2/json-stat-java-0.2.2.jar +0 -0
- data/lib/mondrian_rest/formatters/jsonstat.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cfb2bb9ca02faf64e851332d271430bed71fcda750ae4f0b46e71cacb8d4c72
|
4
|
+
data.tar.gz: 82aa47f20dabcfb4a8deff1e8636f9a8570b667956baf90db522351d6c84a0c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
17
|
-
path = api.
|
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'
|
data/lib/mondrian_rest/api.rb
CHANGED
@@ -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,
|
22
|
+
content_type :xls, 'application/vnd.ms-excel'
|
24
23
|
formatter :xls, Mondrian::REST::Formatters::XLS
|
25
24
|
|
26
|
-
content_type :csv,
|
25
|
+
content_type :csv, 'text/csv'
|
27
26
|
formatter :csv, Mondrian::REST::Formatters::CSV
|
28
27
|
|
29
|
-
content_type :json,
|
28
|
+
content_type :json, 'application/json'
|
30
29
|
formatter :json, Mondrian::REST::Formatters::AggregationJSON
|
31
30
|
|
32
|
-
content_type :jsonrecords,
|
31
|
+
content_type :jsonrecords, 'application/x-jsonrecords'
|
33
32
|
formatter :jsonrecords, Mondrian::REST::Formatters::JSONRecords
|
34
33
|
|
35
|
-
desc
|
36
|
-
content_type :txt,
|
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:
|
41
|
-
optional :properties, type: Array, desc:
|
42
|
-
optional :caption, type: Array, desc:
|
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:
|
54
|
+
requires :secret, type: String, desc: 'Secret key'
|
56
55
|
end
|
57
|
-
content_type :json,
|
58
|
-
desc
|
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!(
|
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!(
|
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,
|
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
|
83
|
+
desc 'Return a cube'
|
85
84
|
params do
|
86
|
-
requires :cube_name, type: String, desc:
|
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
|
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
|
-
|
111
|
-
|
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,
|
116
|
+
content_type :xls, 'application/vnd.ms-excel'
|
119
117
|
formatter :xls, Mondrian::REST::Formatters::XLS
|
120
118
|
|
121
|
-
content_type :csv,
|
119
|
+
content_type :csv, 'text/csv'
|
122
120
|
formatter :csv, Mondrian::REST::Formatters::CSV
|
123
121
|
|
124
|
-
content_type :json,
|
122
|
+
content_type :json, 'application/json'
|
125
123
|
formatter :json, Mondrian::REST::Formatters::AggregationJSON
|
126
124
|
|
127
|
-
content_type :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
|
132
|
+
desc 'aggregate from query parameters'
|
138
133
|
params do
|
139
134
|
optional :measures, type: Array
|
140
|
-
optional :cut, type: Array, desc:
|
141
|
-
optional :drilldown, type: Array, desc:
|
142
|
-
optional :nonempty, type: Boolean, desc:
|
143
|
-
optional :sparse, type: Boolean, desc:
|
144
|
-
optional :distinct, type: Boolean, desc:
|
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:
|
147
|
-
optional :properties, type: Array, desc:
|
148
|
-
optional :caption, type: Array, desc:
|
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:
|
165
|
-
requires :dimension_name, type: String, desc:
|
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:
|
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
|
-
|
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:
|
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
|
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(
|
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[:
|
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
|
-
|
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
|
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
|
-
|
62
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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.
|
81
|
+
pvalues = cm.each.with_index.each_with_object({}) do |(member, ax_i), h|
|
99
82
|
dname = dimensions[ax_i][:name]
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
70
|
-
|
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?
|
74
|
-
return {}
|
75
|
-
end
|
97
|
+
return {} if cprops.nil? || cprops.empty?
|
76
98
|
|
77
|
-
NEST.map(cprops.map
|
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
|
-
:
|
177
|
-
:
|
178
|
-
:
|
179
|
-
:
|
180
|
-
:
|
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
|
-
|
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
|
-
|
207
|
-
|
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.
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
240
|
-
|
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
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
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?
|
49
|
-
error!(
|
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
|
-
|
62
|
-
error!(
|
62
|
+
&& (p.getArgList.first.getOperatorName == ':')
|
63
|
+
error!('Illegal cut: ' + cut_expr, 400)
|
63
64
|
end
|
64
65
|
|
65
|
-
ls = p.getArgList.first.getArgList.map
|
66
|
+
ls = p.getArgList.first.getArgList.map do |id_node|
|
66
67
|
get_member(cube, unparse_node(id_node)).raw_level
|
67
|
-
|
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!(
|
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
|
-
|
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!(
|
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
|
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
|
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
|
-
|
162
|
-
|
163
|
-
|
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'].
|
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
|
-
|
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.
|
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
|
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
|
-
|
254
|
+
'{' + cut[:set_members].map do |m|
|
204
255
|
"DESCENDANTS(#{m.full_name}, #{qa.unique_name})"
|
205
|
-
|
256
|
+
end.join(',') + '}'
|
206
257
|
when :range
|
207
258
|
# TODO
|
208
|
-
raise
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
ds)
|
266
|
+
unless dd.empty?
|
267
|
+
# Cross join all the drilldowns
|
268
|
+
axis_exp = dd.join(' * ')
|
219
269
|
|
220
|
-
|
221
|
-
|
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
|
-
|
225
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
305
|
+
end.join(' * '))
|
241
306
|
end
|
242
307
|
query
|
243
308
|
end
|
data/mondrian-rest.gemspec
CHANGED
@@ -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.
|
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:
|
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:
|
Binary file
|
data/lib/jars/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar
DELETED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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
|
Binary file
|
@@ -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
|