rubiks 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +12 -12
- data/Gemfile +2 -0
- data/README.md +90 -72
- data/lib/rubiks/calculated_measure.rb +26 -0
- data/lib/rubiks/cube.rb +80 -0
- data/lib/rubiks/dimension.rb +48 -0
- data/lib/rubiks/hierarchy.rb +46 -0
- data/lib/rubiks/jars/commons-collections.jar +0 -0
- data/lib/rubiks/jars/commons-dbcp.jar +0 -0
- data/lib/rubiks/jars/commons-logging.jar +0 -0
- data/lib/rubiks/jars/commons-math.jar +0 -0
- data/lib/rubiks/jars/commons-pool.jar +0 -0
- data/lib/rubiks/jars/commons-vfs.jar +0 -0
- data/lib/rubiks/jars/eigenbase-properties.jar +0 -0
- data/lib/rubiks/jars/eigenbase-resgen.jar +0 -0
- data/lib/rubiks/jars/eigenbase-xom.jar +0 -0
- data/lib/rubiks/jars/log4j.jar +0 -0
- data/lib/rubiks/jars/mondrian.jar +0 -0
- data/lib/rubiks/jars/olap4j-1.2.0-SNAPSHOT.jar +0 -0
- data/lib/rubiks/jars/olap4j-xmla-1.2.0-SNAPSHOT.jar +0 -0
- data/lib/rubiks/jars/olap4j-xmla.jar.orig +0 -0
- data/lib/rubiks/jars/olap4j-xmlaserver-0.0.1-SNAPSHOT.jar +0 -0
- data/lib/rubiks/jars/olap4j.jar.orig +0 -0
- data/lib/rubiks/jars/postgresql-9.2-1002.jdbc3.jar +0 -0
- data/lib/rubiks/jars/postgresql-9.2-1002.jdbc4.jar +0 -0
- data/lib/rubiks/level.rb +57 -0
- data/lib/rubiks/measure.rb +28 -0
- data/lib/rubiks/mondrian/cell_set.rb +148 -0
- data/lib/rubiks/mondrian/connection.rb +131 -0
- data/lib/rubiks/mondrian/errors.rb +56 -0
- data/lib/rubiks/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/rubiks/mondrian/jars/commons-dbcp-1.2.2.jar +0 -0
- data/lib/rubiks/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/rubiks/mondrian/jars/commons-math-1.2.jar +0 -0
- data/lib/rubiks/mondrian/jars/commons-pool-1.4.jar +0 -0
- data/lib/rubiks/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
- data/lib/rubiks/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
- data/lib/rubiks/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/rubiks/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/rubiks/mondrian/jars/mondrian-4.0.0-SNAPSHOT.jar +0 -0
- data/lib/rubiks/mondrian/jars/olap4j-1.2.0-SNAPSHOT.jar +0 -0
- data/lib/rubiks/mondrian/jars/olap4j-xmla-1.2.0-SNAPSHOT.jar +0 -0
- data/lib/rubiks/mondrian/jars/olap4j-xmlaserver-0.0.1-SNAPSHOT.jar +0 -0
- data/lib/rubiks/mondrian/member.rb +107 -0
- data/lib/rubiks/mondrian.rb +44 -0
- data/lib/rubiks/named_object.rb +121 -0
- data/lib/rubiks/schema.rb +28 -0
- data/lib/rubiks/version.rb +1 -1
- data/lib/rubiks.rb +28 -4
- data/rubiks.gemspec +2 -1
- data/spec/examples/simple_mondrian_schema_spec.rb +56 -0
- data/spec/rubiks/calculated_measure_spec.rb +12 -0
- data/spec/rubiks/cube_spec.rb +29 -0
- data/spec/rubiks/dimension_spec.rb +12 -0
- data/spec/rubiks/hierarchy_spec.rb +15 -0
- data/spec/rubiks/level_spec.rb +20 -0
- data/spec/rubiks/measure_spec.rb +11 -0
- data/spec/rubiks/schema_spec.rb +32 -0
- data/spec/spec_helper.rb +10 -3
- data/spec/support/shared_examples.rb +12 -0
- metadata +85 -47
- data/lib/rubiks/examples.rb +0 -61
- data/lib/rubiks/nodes/annotated_node.rb +0 -26
- data/lib/rubiks/nodes/calculated_member.rb +0 -81
- data/lib/rubiks/nodes/cube.rb +0 -149
- data/lib/rubiks/nodes/dimension.rb +0 -72
- data/lib/rubiks/nodes/hierarchy.rb +0 -100
- data/lib/rubiks/nodes/level.rb +0 -133
- data/lib/rubiks/nodes/measure.rb +0 -75
- data/lib/rubiks/nodes/schema.rb +0 -72
- data/lib/rubiks/nodes/validated_node.rb +0 -79
- data/spec/examples/mondrian_docs_example_spec.rb +0 -108
- data/spec/examples/simple_mondrian_example_spec.rb +0 -84
- data/spec/rubiks/nodes/annotated_node_spec.rb +0 -24
- data/spec/rubiks/nodes/calculated_member_spec.rb +0 -50
- data/spec/rubiks/nodes/cube_spec.rb +0 -63
- data/spec/rubiks/nodes/dimension_spec.rb +0 -41
- data/spec/rubiks/nodes/hierarchy_spec.rb +0 -48
- data/spec/rubiks/nodes/level_spec.rb +0 -93
- data/spec/rubiks/nodes/measure_spec.rb +0 -55
- data/spec/rubiks/nodes/schema_spec.rb +0 -38
- data/spec/rubiks/nodes/validated_node_spec.rb +0 -49
- data/spec/support/examples/mondrian_docs.yml +0 -36
- data/spec/support/examples/shared_dimensions.yml +0 -45
- data/spec/support/matchers/be_like.rb +0 -24
- data/spec/support/schema_context.rb +0 -59
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'rubiks/mondrian/member'
|
2
|
+
|
3
|
+
module ::Rubiks
|
4
|
+
module Mondrian
|
5
|
+
|
6
|
+
class CellSet
|
7
|
+
def initialize(raw_cell_set)
|
8
|
+
@raw_cell_set = raw_cell_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#<#{self.class}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
axes_strings = []
|
17
|
+
axes_strings << "column_axis=#{axis_string(column_axis)}"
|
18
|
+
axes_strings << "row_axis=#{axis_string(row_axis)}" if row_axis
|
19
|
+
axes_strings << "filter_axis=#{axis_string(filter_axis)}" if filter_axis
|
20
|
+
|
21
|
+
"#<#{self.class} cube=#{cube_name} #{axes_strings.join(' ')}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{
|
26
|
+
:cube_name => cube_name,
|
27
|
+
:cells => cells,
|
28
|
+
:axes => result_axes,
|
29
|
+
:filter_axis => filter_axis
|
30
|
+
}.delete_if{ |k,v| v.blank? }
|
31
|
+
end
|
32
|
+
|
33
|
+
def cells
|
34
|
+
raw_cells.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def cube_name
|
38
|
+
@raw_cell_set.meta_data.cube.name
|
39
|
+
end
|
40
|
+
|
41
|
+
def filter_axis
|
42
|
+
generate_axis(@raw_cell_set.filter_axis)
|
43
|
+
end
|
44
|
+
|
45
|
+
def column_axis
|
46
|
+
result_axes.first
|
47
|
+
end
|
48
|
+
|
49
|
+
def row_axis
|
50
|
+
result_axes[1]
|
51
|
+
end
|
52
|
+
|
53
|
+
def axes
|
54
|
+
@axes ||= @raw_cell_set.getAxes
|
55
|
+
end
|
56
|
+
|
57
|
+
def result_axes
|
58
|
+
axes.map{ |raw_axis| generate_axis(raw_axis) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def axis_string(axis)
|
62
|
+
axis[:tuples].map do |tuple|
|
63
|
+
members = tuple[:members].map{ |member| member[:path] }.join(',')
|
64
|
+
end.join('|')
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_axis(axis)
|
68
|
+
tuples = axis.getPositions.map do |position|
|
69
|
+
|
70
|
+
members = position.getMembers.map do |raw_member|
|
71
|
+
member = ::Rubiks::Mondrian::Member.new(raw_member)
|
72
|
+
|
73
|
+
names = [raw_member.level.name]
|
74
|
+
names << raw_member.level.hierarchy.name.split('.').last
|
75
|
+
names << raw_member.level.hierarchy.dimension.name
|
76
|
+
|
77
|
+
level_unique_name = names.reverse.flatten.map{ |str| "[#{str}]" }.join('.')
|
78
|
+
member_unique_name = "#{level_unique_name}.[#{raw_member.name}]"
|
79
|
+
|
80
|
+
path_name = '/' + raw_member.unique_name.gsub(/\]\.\[/, '/')[1...-1]
|
81
|
+
|
82
|
+
{
|
83
|
+
:name => raw_member.name,
|
84
|
+
:unique_name => generate_unique_name(member_unique_name),
|
85
|
+
:level_name => generate_unique_name(level_unique_name),
|
86
|
+
:path => path_name,
|
87
|
+
:level_depth => raw_member.depth,
|
88
|
+
:child_count => member.children.length,
|
89
|
+
:is_all_member => member.all_member?,
|
90
|
+
:is_drillable => member.drillable?
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
members.compact!
|
95
|
+
|
96
|
+
if members.present?
|
97
|
+
{:members => members}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
tuples.compact!
|
102
|
+
|
103
|
+
if tuples.present?
|
104
|
+
{
|
105
|
+
:name => axis.axis_ordinal.name,
|
106
|
+
:tuples => tuples
|
107
|
+
}
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# [Date.YQMD] - Mondrian's hierarchy
|
114
|
+
# [Logins].[Date].[YQMD] - needed output
|
115
|
+
def generate_unique_name(string)
|
116
|
+
cube_name = @raw_cell_set.meta_data.cube.name
|
117
|
+
dimension_hierarchy_regexp = /\[([^\]]+)\.([^\]]+)\]/
|
118
|
+
formatted_string = string.
|
119
|
+
gsub(dimension_hierarchy_regexp, '[\1].[\2]').
|
120
|
+
gsub(/\[Measures\]\.\[Measures\]/, '[Measures]').
|
121
|
+
gsub(/\.\[MeasuresLevel\]/, '')
|
122
|
+
|
123
|
+
"[#{cube_name}].#{formatted_string}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def raw_cells
|
127
|
+
axes_sequence = (0...axes.size).to_a.reverse
|
128
|
+
recursive_values(axes_sequence, 0)
|
129
|
+
end
|
130
|
+
|
131
|
+
def recursive_values(axes_sequence, current_index, cell_params=[])
|
132
|
+
if axis_number = axes_sequence[current_index]
|
133
|
+
(0...axes[axis_number].getPositions.size).map do |i|
|
134
|
+
cell_params[axis_number] = Java::JavaLang::Integer.new(i)
|
135
|
+
recursive_values(axes_sequence, current_index + 1, cell_params)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
cell = @raw_cell_set.getCell(cell_params)
|
139
|
+
{
|
140
|
+
:value => cell.value,
|
141
|
+
:formatted_value => cell.formatted_value
|
142
|
+
}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Taken from mondrian-olap: https://github.com/rsim/mondrian-olap/blob/master/lib/mondrian/olap/connection.rb
|
2
|
+
require 'rubiks/mondrian/cell_set'
|
3
|
+
require 'rubiks/mondrian/errors'
|
4
|
+
|
5
|
+
module ::Rubiks
|
6
|
+
module Mondrian
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
def self.create(params)
|
10
|
+
connection = new(params)
|
11
|
+
connection.connect
|
12
|
+
connection
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :raw_connection, :raw_catalog, :raw_schema
|
16
|
+
|
17
|
+
def initialize(params={})
|
18
|
+
@params = params
|
19
|
+
@driver = params[:driver]
|
20
|
+
@connected = false
|
21
|
+
@raw_connection = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
::Rubiks::MondrianError.wrap_native_exception do
|
26
|
+
# hack to call private constructor of MondrianOlap4jDriver
|
27
|
+
# to avoid using DriverManager which fails to load JDBC drivers
|
28
|
+
# because of not seeing JRuby required jar files
|
29
|
+
cons = Java::MondrianOlap4j::MondrianOlap4jDriver.java_class.declared_constructor
|
30
|
+
cons.accessible = true
|
31
|
+
driver = cons.new_instance.to_java
|
32
|
+
|
33
|
+
props = java.util.Properties.new
|
34
|
+
props.setProperty('JdbcUser', @params[:username]) if @params[:username]
|
35
|
+
props.setProperty('JdbcPassword', @params[:password]) if @params[:password]
|
36
|
+
|
37
|
+
conn_string = connection_string
|
38
|
+
@raw_jdbc_connection = driver.connect(conn_string, props)
|
39
|
+
|
40
|
+
@raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
|
41
|
+
@raw_catalog = @raw_connection.getOlapCatalog
|
42
|
+
# currently it is assumed that there is just one schema per connection catalog
|
43
|
+
@raw_schema = @raw_catalog.getSchemas.first
|
44
|
+
@connected = true
|
45
|
+
true
|
46
|
+
|
47
|
+
# latest Mondrian version added ClassResolver which uses current thread class loader to load some classes
|
48
|
+
# therefore need to set it to JRuby class loader to ensure that Mondrian classes are found
|
49
|
+
# (e.g. when running mondrian-olap inside OSGi container)
|
50
|
+
current_thread = Java::JavaLang::Thread.currentThread
|
51
|
+
class_loader = current_thread.getContextClassLoader
|
52
|
+
begin
|
53
|
+
current_thread.setContextClassLoader JRuby.runtime.jruby_class_loader
|
54
|
+
@raw_jdbc_connection = driver.connect(conn_string, props)
|
55
|
+
ensure
|
56
|
+
current_thread.setContextClassLoader(class_loader)
|
57
|
+
end
|
58
|
+
|
59
|
+
@raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
|
60
|
+
@raw_catalog = @raw_connection.getOlapCatalog
|
61
|
+
# currently it is assumed that there is just one schema per connection catalog
|
62
|
+
@raw_schema = @raw_catalog.getSchemas.first
|
63
|
+
@connected = true
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def connected?
|
69
|
+
@connected
|
70
|
+
end
|
71
|
+
|
72
|
+
def close
|
73
|
+
@raw_connection.close
|
74
|
+
@connected = false
|
75
|
+
@raw_connection = @raw_jdbc_connection = nil
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def execute(query_string)
|
80
|
+
::Rubiks::MondrianError.wrap_native_exception do
|
81
|
+
statement = @raw_connection.prepareOlapStatement(query_string)
|
82
|
+
::Rubiks::Mondrian::CellSet.new(statement.executeQuery())
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def connection_string
|
89
|
+
string = "jdbc:mondrian:Jdbc=#{quote_string(jdbc_uri)};JdbcDrivers=#{jdbc_driver};"
|
90
|
+
# by default use content checksum to reload schema when catalog has changed
|
91
|
+
string << "UseContentChecksum=true;" unless @params[:use_content_checksum] == false
|
92
|
+
if role = @params[:role] || @params[:roles]
|
93
|
+
roles = Array(role).map{|r| r && r.to_s.gsub(',', ',,')}.compact
|
94
|
+
string << "Role=#{quote_string(roles.join(','))};" unless roles.empty?
|
95
|
+
end
|
96
|
+
string << (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{quote_string(catalog_content)}")
|
97
|
+
end
|
98
|
+
|
99
|
+
def jdbc_uri
|
100
|
+
"jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def jdbc_driver
|
104
|
+
'org.postgresql.Driver'
|
105
|
+
end
|
106
|
+
|
107
|
+
def catalog_uri
|
108
|
+
if @params[:catalog]
|
109
|
+
"file://#{File.expand_path(@params[:catalog])}"
|
110
|
+
else
|
111
|
+
raise ArgumentError, 'missing catalog source'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def catalog_content
|
116
|
+
if @params[:catalog_content]
|
117
|
+
@params[:catalog_content]
|
118
|
+
elsif @params[:schema]
|
119
|
+
@params[:schema].to_xml(:driver => @driver)
|
120
|
+
else
|
121
|
+
raise ArgumentError, "Specify catalog with :catalog, :catalog_content or :schema option"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def quote_string(string)
|
126
|
+
"'#{string.gsub("'","''")}'"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Taken from mondrian-olap: https://github.com/rsim/mondrian-olap/blob/master/lib/mondrian/olap/error.rb
|
2
|
+
module ::Rubiks
|
3
|
+
|
4
|
+
class MondrianError < StandardError
|
5
|
+
NATIVE_MONDRIAN_ERROR_REGEXP = /^(org\.olap4j\.|mondrian\.|java\.lang\.reflect\.UndeclaredThrowableException\: Mondrian Error\:)/
|
6
|
+
|
7
|
+
# root_cause will be nil if there is no cause for wrapped native error
|
8
|
+
# root_cause_message will have either root_cause message or wrapped native error message
|
9
|
+
attr_reader :native_error, :root_cause_message, :root_cause
|
10
|
+
|
11
|
+
def initialize(native_error)
|
12
|
+
@native_error = native_error
|
13
|
+
get_root_cause
|
14
|
+
super(native_error.message)
|
15
|
+
add_root_cause_to_backtrace
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.wrap_native_exception
|
19
|
+
yield
|
20
|
+
rescue NativeException => e
|
21
|
+
if e.message =~ NATIVE_MONDRIAN_ERROR_REGEXP
|
22
|
+
raise ::Rubiks::MondrianError.new(e)
|
23
|
+
else
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_root_cause
|
31
|
+
@root_cause = nil
|
32
|
+
e = @native_error
|
33
|
+
while e.respond_to?(:cause) && (cause = e.cause)
|
34
|
+
@root_cause = e = cause
|
35
|
+
end
|
36
|
+
message = e.message
|
37
|
+
if message =~ /\AMondrian Error:(.*)\Z/m
|
38
|
+
message = $1
|
39
|
+
end
|
40
|
+
@root_cause_message = message
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_root_cause_to_backtrace
|
44
|
+
bt = @native_error.backtrace
|
45
|
+
if @root_cause
|
46
|
+
root_cause_bt = Array(@root_cause.backtrace)
|
47
|
+
root_cause_bt[0,5].reverse.each do |bt_line|
|
48
|
+
bt.unshift "root cause: #{bt_line}"
|
49
|
+
end
|
50
|
+
bt.unshift "root cause: #{@root_cause.java_class.name}: #{@root_cause.message.chomp}"
|
51
|
+
end
|
52
|
+
set_backtrace bt
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Taken from mondrian-olap: https://github.com/rsim/mondrian-olap/blob/master/lib/mondrian/olap/cube.rb
|
2
|
+
# require 'rubiks/mondrian/cell_set'
|
3
|
+
# require 'rubiks/mondrian/errors'
|
4
|
+
|
5
|
+
module ::Rubiks
|
6
|
+
module Mondrian
|
7
|
+
|
8
|
+
class Member
|
9
|
+
def initialize(raw_member)
|
10
|
+
@raw_member = raw_member
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :raw_member
|
14
|
+
|
15
|
+
def name
|
16
|
+
@raw_member.getName
|
17
|
+
end
|
18
|
+
|
19
|
+
def full_name
|
20
|
+
@raw_member.getUniqueName
|
21
|
+
end
|
22
|
+
|
23
|
+
def caption
|
24
|
+
@raw_member.getCaption
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculated?
|
28
|
+
@raw_member.isCalculated
|
29
|
+
end
|
30
|
+
|
31
|
+
def calculated_in_query?
|
32
|
+
@raw_member.isCalculatedInQuery
|
33
|
+
end
|
34
|
+
|
35
|
+
def visible?
|
36
|
+
@raw_member.isVisible
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_member?
|
40
|
+
@raw_member.isAll
|
41
|
+
end
|
42
|
+
|
43
|
+
def drillable?
|
44
|
+
return false if calculated?
|
45
|
+
# @raw_member.getChildMemberCount > 0
|
46
|
+
# This hopefully is faster than counting actual child members
|
47
|
+
raw_level = @raw_member.getLevel
|
48
|
+
raw_levels = raw_level.getHierarchy.getLevels
|
49
|
+
raw_levels.indexOf(raw_level) < raw_levels.size - 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def depth
|
53
|
+
@raw_member.getDepth
|
54
|
+
end
|
55
|
+
|
56
|
+
def dimension_type
|
57
|
+
case @raw_member.getDimension.getDimensionType
|
58
|
+
when Java::OrgOlap4jMetadata::Dimension::Type::TIME
|
59
|
+
:time
|
60
|
+
when Java::OrgOlap4jMetadata::Dimension::Type::MEASURE
|
61
|
+
:measures
|
62
|
+
else
|
63
|
+
:standard
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def children
|
68
|
+
::Rubiks::MondrianError.wrap_native_exception do
|
69
|
+
@raw_member.getChildMembers.map{|m| ::Rubiks::Mondrian::Member.new(m)}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def descendants_at_level(level)
|
74
|
+
::Rubiks::MondrianError.wrap_native_exception do
|
75
|
+
raw_level = @raw_member.getLevel
|
76
|
+
raw_levels = raw_level.getHierarchy.getLevels
|
77
|
+
current_level_index = raw_levels.indexOf(raw_level)
|
78
|
+
descendants_level_index = raw_levels.indexOfName(level)
|
79
|
+
|
80
|
+
return nil unless descendants_level_index > current_level_index
|
81
|
+
|
82
|
+
members = [self]
|
83
|
+
(descendants_level_index - current_level_index).times do
|
84
|
+
members = members.map do |member|
|
85
|
+
member.children
|
86
|
+
end.flatten
|
87
|
+
end
|
88
|
+
members
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def property_value(name)
|
93
|
+
if property = @raw_member.getProperties.get(name)
|
94
|
+
@raw_member.getPropertyValue(property)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def property_formatted_value(name)
|
99
|
+
if property = @raw_member.getProperties.get(name)
|
100
|
+
@raw_member.getPropertyFormattedValue(property)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'rubiks/mondrian/connection'
|
3
|
+
require 'rubiks/mondrian/errors'
|
4
|
+
|
5
|
+
Dir[File.expand_path('../mondrian/jars/*.jar', __FILE__)].each{ |jar| require jar }
|
6
|
+
|
7
|
+
# register Mondrian olap4j driver
|
8
|
+
Java::mondrian.olap4j.MondrianOlap4jDriver
|
9
|
+
|
10
|
+
module ::Rubiks
|
11
|
+
def self.connection
|
12
|
+
@connection ||= ::Rubiks::Mondrian.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
module Mondrian
|
16
|
+
def self.connection
|
17
|
+
@connection ||= ::Rubiks::Mondrian::Connection.create(
|
18
|
+
config.merge(:catalog_content => ::Rubiks.schema.to_xml)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config=(new_config)
|
23
|
+
@config = new_config
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.config
|
27
|
+
@config ||= begin
|
28
|
+
if defined?(ActiveRecord)
|
29
|
+
ar_config = ActiveRecord::Base.connection.config
|
30
|
+
|
31
|
+
{
|
32
|
+
:driver => ar_config[:adapter],
|
33
|
+
:host => ar_config[:host],
|
34
|
+
:database => ar_config[:database],
|
35
|
+
:username => ar_config[:username],
|
36
|
+
:password => ar_config[:password]
|
37
|
+
}
|
38
|
+
else
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module ::Rubiks
|
2
|
+
|
3
|
+
class NamedObject
|
4
|
+
def self.define(new_name=nil, options={}, &block)
|
5
|
+
instance = new(new_name, options)
|
6
|
+
instance.instance_eval(&block) if block_given?
|
7
|
+
instances[instance.name.to_sym] = instance
|
8
|
+
instance
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.instances
|
12
|
+
@instances ||= Hash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_or_create(instance_name, options={}, &block)
|
16
|
+
return instances[instance_name.to_sym] if instances.has_key?(instance_name.to_sym)
|
17
|
+
|
18
|
+
new_instance = new(instance_name.to_s, options)
|
19
|
+
new_instance.instance_eval(&block) if block_given?
|
20
|
+
new_instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.clear!
|
24
|
+
@instances = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.default
|
28
|
+
if instances.has_key?('default')
|
29
|
+
instances['default']
|
30
|
+
elsif instances.present?
|
31
|
+
instances.first[1]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.[](instance_name)
|
36
|
+
instances[instance_name.to_sym]
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(new_name = nil, options = {})
|
40
|
+
@name = new_name.to_s if new_name.present?
|
41
|
+
@options = options.symbolize_keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def name(new_value=nil)
|
45
|
+
@name = new_value.to_s if new_value.present?
|
46
|
+
@name ||= @options[:name] || 'default'
|
47
|
+
end
|
48
|
+
|
49
|
+
def icon_type(new_value=nil)
|
50
|
+
@icon_type = new_value.to_s if new_value.present?
|
51
|
+
@icon_type ||= @options[:icon_type]
|
52
|
+
end
|
53
|
+
|
54
|
+
def description(new_value=nil)
|
55
|
+
@description = new_value.to_s if new_value.present?
|
56
|
+
@description ||= @options[:description]
|
57
|
+
end
|
58
|
+
|
59
|
+
def visible(new_value=nil)
|
60
|
+
@visible = new_value.to_s unless new_value.nil?
|
61
|
+
@visible ||= @options.key?(:visible) ? @options[:visible].to_s : nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def column(new_value=nil)
|
65
|
+
@column = new_value.to_s if new_value.present?
|
66
|
+
@column ||= @options[:column] || name
|
67
|
+
end
|
68
|
+
|
69
|
+
def caption(new_value=nil)
|
70
|
+
@caption = new_value if new_value.present?
|
71
|
+
@caption ||= @options[:caption] || name.titleize
|
72
|
+
end
|
73
|
+
|
74
|
+
def table(new_value=nil)
|
75
|
+
@table = new_value if new_value.present?
|
76
|
+
@table ||= @options[:table] || "view_#{name.tableize}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_json(options={})
|
80
|
+
MultiJson.encode(json_hash, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_json_attributes
|
84
|
+
json_attrs = {
|
85
|
+
:name => name,
|
86
|
+
:caption => caption,
|
87
|
+
:description => description,
|
88
|
+
:icon_type => icon_type
|
89
|
+
}
|
90
|
+
json_attrs[:visible] = visible if visible.present? && visible == 'false'
|
91
|
+
json_attrs.delete_if { |key,value| value.nil? }
|
92
|
+
json_attrs.stringify_keys!
|
93
|
+
end
|
94
|
+
alias_method :json_hash, :default_json_attributes
|
95
|
+
|
96
|
+
def default_xml_attributes
|
97
|
+
xml_attrs = {
|
98
|
+
:name => caption,
|
99
|
+
:description => description
|
100
|
+
}
|
101
|
+
xml_attrs[:visible] = visible if visible.present? && visible == 'false'
|
102
|
+
xml_attrs.delete_if { |key,value| value.nil? }
|
103
|
+
end
|
104
|
+
alias_method :xml_hash, :default_xml_attributes
|
105
|
+
|
106
|
+
def to_xml(builder = nil)
|
107
|
+
return if name.blank?
|
108
|
+
|
109
|
+
builder = builder || new_builder
|
110
|
+
|
111
|
+
builder.__send__(name)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def new_builder
|
117
|
+
builder = Builder::XmlMarkup.new(:indent => 2) if builder.nil?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ::Rubiks
|
2
|
+
|
3
|
+
class Schema < NamedObject
|
4
|
+
def cubes
|
5
|
+
@cubes ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def cube(cube_name, options={}, &block)
|
9
|
+
cubes.push ::Rubiks::Cube.find_or_create(cube_name, options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def json_hash
|
13
|
+
hash = default_json_attributes
|
14
|
+
hash[:cubes] = cubes.map{ |c| c.json_hash } if cubes.present?
|
15
|
+
hash.stringify_keys!
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_xml(builder = nil)
|
19
|
+
builder = builder || new_builder
|
20
|
+
builder.instruct!
|
21
|
+
|
22
|
+
builder.schema(:name => caption) do
|
23
|
+
cubes.each{ |cube| cube.to_xml(builder) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/rubiks/version.rb
CHANGED