mondrian-rest 0.7.9-java → 1.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|