caruby-core 1.4.7 → 1.4.9
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.
- data/History.txt +11 -0
- data/README.md +1 -1
- data/lib/caruby/cli/command.rb +27 -3
- data/lib/caruby/csv/csv_mapper.rb +2 -0
- data/lib/caruby/csv/csvio.rb +187 -169
- data/lib/caruby/database.rb +33 -16
- data/lib/caruby/database/lazy_loader.rb +23 -23
- data/lib/caruby/database/persistable.rb +32 -18
- data/lib/caruby/database/persistence_service.rb +20 -7
- data/lib/caruby/database/reader.rb +22 -21
- data/lib/caruby/database/search_template_builder.rb +7 -9
- data/lib/caruby/database/sql_executor.rb +52 -27
- data/lib/caruby/database/store_template_builder.rb +18 -13
- data/lib/caruby/database/writer.rb +107 -44
- data/lib/caruby/domain/attribute_metadata.rb +35 -25
- data/lib/caruby/domain/java_attribute_metadata.rb +43 -20
- data/lib/caruby/domain/merge.rb +9 -5
- data/lib/caruby/domain/reference_visitor.rb +4 -3
- data/lib/caruby/domain/resource_attributes.rb +52 -12
- data/lib/caruby/domain/resource_dependency.rb +129 -42
- data/lib/caruby/domain/resource_introspection.rb +1 -1
- data/lib/caruby/domain/resource_inverse.rb +20 -3
- data/lib/caruby/domain/resource_metadata.rb +20 -4
- data/lib/caruby/domain/resource_module.rb +190 -124
- data/lib/caruby/import/java.rb +39 -19
- data/lib/caruby/migration/migratable.rb +31 -6
- data/lib/caruby/migration/migrator.rb +126 -40
- data/lib/caruby/migration/uniquify.rb +0 -1
- data/lib/caruby/resource.rb +28 -5
- data/lib/caruby/util/attribute_path.rb +0 -2
- data/lib/caruby/util/class.rb +8 -5
- data/lib/caruby/util/collection.rb +5 -3
- data/lib/caruby/util/domain_extent.rb +0 -3
- data/lib/caruby/util/options.rb +10 -9
- data/lib/caruby/util/person.rb +41 -12
- data/lib/caruby/util/pretty_print.rb +1 -1
- data/lib/caruby/util/validation.rb +0 -28
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +26 -9
- data/test/lib/caruby/migration/test_case.rb +103 -0
- data/test/lib/caruby/test_case.rb +231 -0
- data/test/lib/caruby/util/class_test.rb +2 -2
- data/test/lib/caruby/util/visitor_test.rb +3 -2
- data/test/lib/examples/galena/clinical_trials/migration/participant_test.rb +28 -0
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +40 -0
- metadata +195 -170
- data/lib/caruby/domain/attribute_initializer.rb +0 -16
- data/test/lib/caruby/util/validation_test.rb +0 -14
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'caruby/util/validation'
|
2
|
+
|
1
3
|
module CaRuby
|
2
4
|
# ResourceMetadata mix-in to capture Resource dependency.
|
3
5
|
module ResourceDependency
|
@@ -25,24 +27,49 @@ module CaRuby
|
|
25
27
|
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
26
28
|
#
|
27
29
|
# @param [Symbol] attribute the dependent to add
|
28
|
-
# @param [<Symbol>] the attribute qualifier flags
|
30
|
+
# @param [<Symbol>] flags the attribute qualifier flags
|
29
31
|
def add_dependent_attribute(attribute, *flags)
|
30
32
|
attr_md = attribute_metadata(attribute)
|
31
33
|
flags << :dependent unless flags.include?(:dependent)
|
32
34
|
attr_md.qualify(*flags)
|
33
35
|
inverse = attr_md.inverse
|
34
36
|
inv_type = attr_md.type
|
35
|
-
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls
|
36
|
-
#
|
37
|
+
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
|
38
|
+
# Child.add_owner(Parent, :children, :parent)
|
37
39
|
inv_type.add_owner(self, attribute, inverse)
|
38
40
|
end
|
39
|
-
|
40
|
-
#
|
41
|
+
|
42
|
+
# Makes a new owner attribute. The attribute name is the lower-case demodulized
|
43
|
+
# owner class name. The owner class must reference this class via the given
|
44
|
+
# inverse dependent attribute.
|
45
|
+
#
|
46
|
+
# @param klass (see #detect_owner_attribute)
|
47
|
+
# @param [Symbol] the owner -> dependent inverse attribute
|
48
|
+
# @return [Symbol] this class's new owner attribute
|
49
|
+
# @raise [ArgumentError] if the inverse is nil
|
50
|
+
def create_owner_attribute(klass, inverse)
|
51
|
+
if inverse.nil? then
|
52
|
+
raise ArgumentError.new("Cannot create a #{qp} owner attribute to #{klass} without a dependent attribute to this class.")
|
53
|
+
end
|
54
|
+
attr = klass.name.demodulize.underscore.to_sym
|
55
|
+
attr_accessor(attr)
|
56
|
+
add_attribute(attr, klass)
|
57
|
+
attribute_metadata(attr).inverse = inverse
|
58
|
+
logger.debug { "Created #{qp} owner attribute #{attr} with inverse #{klass.qp}.#{inverse}." }
|
59
|
+
attr
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Boolean] whether this class depends on an owner
|
41
63
|
def dependent?
|
42
64
|
not owners.empty?
|
43
65
|
end
|
66
|
+
|
67
|
+
# @return [Boolean] whether this class has an owner which cascades save operations to this dependent
|
68
|
+
def cascaded_dependent?
|
69
|
+
owner_attribute_metadata_enumerator.any? { |attr_md| attr_md.cascaded? }
|
70
|
+
end
|
44
71
|
|
45
|
-
#
|
72
|
+
# @return [Boolean] whether this class depends the given other class
|
46
73
|
def depends_on?(other)
|
47
74
|
owners.detect { |owner| owner === other }
|
48
75
|
end
|
@@ -58,73 +85,133 @@ module CaRuby
|
|
58
85
|
# @return [Symbol, nil] the sole owner attribute of this class, or nil if there
|
59
86
|
# is not exactly one owner
|
60
87
|
def owner_attribute
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
superclass.owner_attribute
|
88
|
+
attr_md = owner_attribute_metadata_enumerator.first || return
|
89
|
+
# There is at most one non-nil value in the owner class => AttributeMetadata hash.
|
90
|
+
# If there is such a value, then return the attribute symbol.
|
91
|
+
if owner_attribute_metadata_enumerator.size == 1 then
|
92
|
+
attr_md.to_sym
|
67
93
|
end
|
68
94
|
end
|
69
95
|
|
70
|
-
#
|
96
|
+
# @return [<Symbol>] this class's owner attributes
|
71
97
|
def owner_attributes
|
72
|
-
|
73
|
-
@local_owner_attrs ||= Enumerable::Enumerator.new(@local_owner_attr_hash, :each_value).filter
|
74
|
-
elsif superclass < Resource
|
75
|
-
superclass.owner_attributes
|
76
|
-
else
|
77
|
-
Array::EMPTY_ARRAY
|
78
|
-
end
|
98
|
+
owner_attribute_metadata_enumerator.transform { |attr_md| attr_md.to_sym }
|
79
99
|
end
|
80
100
|
|
81
|
-
#
|
101
|
+
# @return [<Class>] this class's dependent types
|
82
102
|
def dependents
|
83
103
|
dependent_attributes.wrap { |attr| attr.type }
|
84
104
|
end
|
85
105
|
|
86
|
-
#
|
106
|
+
# @return [<Class>] this class's owner types
|
87
107
|
def owners
|
88
|
-
|
89
|
-
@local_owners ||= Enumerable::Enumerator.new(@local_owner_attr_hash, :each_key)
|
90
|
-
elsif superclass < Resource
|
91
|
-
superclass.owners
|
92
|
-
else
|
93
|
-
Array::EMPTY_ARRAY
|
94
|
-
end
|
108
|
+
@owners ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_key)
|
95
109
|
end
|
96
110
|
|
97
111
|
protected
|
98
112
|
|
99
113
|
# Adds the given owner class to this dependent class.
|
100
114
|
# This method must be called before any dependent attribute is accessed.
|
115
|
+
# If the attribute is given, then the attribute inverse is set.
|
116
|
+
# Otherwise, if there is not already an owner attribute, then a new owner attribute is created.
|
117
|
+
# The name of the new attribute is the lower-case demodulized owner class name.
|
101
118
|
#
|
102
119
|
# @param [Class] the owner class
|
103
|
-
# @param [Symbol
|
120
|
+
# @param [Symbol] inverse the owner -> dependent attribute
|
104
121
|
# @param [Symbol, nil] attribute the dependent -> owner attribute, if known
|
105
|
-
# @raise [ValidationError] if
|
106
|
-
# @raise [MetadataError] if this method is called after a dependent attribute has been accessed
|
122
|
+
# @raise [ValidationError] if the inverse is nil
|
107
123
|
def add_owner(klass, inverse, attribute=nil)
|
124
|
+
if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
|
108
125
|
logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute}#{' inverse ' + inverse.to_s if inverse}..." }
|
109
126
|
if @owner_attr_hash then
|
110
127
|
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
111
128
|
end
|
112
|
-
|
113
|
-
|
129
|
+
|
130
|
+
# detect the owner attribute, if necessary
|
131
|
+
attribute ||= detect_owner_attribute(klass, inverse)
|
132
|
+
attr_md = attribute_metadata(attribute) if attribute
|
133
|
+
# Add the owner class => attribute entry.
|
134
|
+
# The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
|
135
|
+
# references this class via a dependency attribute but there is no inverse owner attribute.
|
136
|
+
local_owner_attribute_metadata_hash[klass] = attr_md
|
137
|
+
# If the dependency is unidirectional, then our job is done.
|
138
|
+
return if attribute.nil?
|
114
139
|
|
115
|
-
# set the inverse
|
116
|
-
|
117
|
-
if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
|
140
|
+
# set the inverse if necessary
|
141
|
+
unless attr_md.inverse then
|
118
142
|
set_attribute_inverse(attribute, inverse)
|
119
|
-
|
143
|
+
end
|
144
|
+
# set the owner flag if necessary
|
145
|
+
unless attr_md.owner? then attr_md.qualify(:owner) end
|
146
|
+
# Redefine the writer method to warn when changing the owner
|
147
|
+
rdr, wtr = attr_md.accessors
|
148
|
+
logger.debug { "Injecting owner change warning into #{qp}.#{attribute} writer method #{wtr}..." }
|
149
|
+
redefine_method(wtr) do |old_wtr|
|
150
|
+
lambda do |ref|
|
151
|
+
prev = send(rdr)
|
152
|
+
if prev and prev != ref then
|
153
|
+
if ref.nil? then
|
154
|
+
logger.warn("Unsetting the #{self} owner #{attribute} #{prev}.")
|
155
|
+
elsif ref.identifier != prev.identifier then
|
156
|
+
logger.warn("Resetting the #{self} owner #{attribute} from #{prev} to #{ref}.")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
send(old_wtr, ref)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Adds the given attribute as an owner. This method is called when a new attribute is added that
|
165
|
+
# references an existing owner.
|
166
|
+
#
|
167
|
+
# @param [Symbol] attribute the owner attribute
|
168
|
+
def add_owner_attribute(attribute)
|
169
|
+
attr_md = attribute_metadata(attribute)
|
170
|
+
otype = attr_md.type
|
171
|
+
hash = local_owner_attribute_metadata_hash
|
172
|
+
if hash.include?(otype) then
|
173
|
+
oattr = hash[otype]
|
174
|
+
unless oattr.nil? then
|
175
|
+
raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oattr}")
|
176
|
+
end
|
177
|
+
hash[otype] = attr_md
|
120
178
|
else
|
121
|
-
|
179
|
+
add_owner(otype, attr_md.inverse, attribute)
|
122
180
|
end
|
123
181
|
end
|
124
182
|
|
125
|
-
# @return [{Class =>
|
126
|
-
def
|
127
|
-
@
|
183
|
+
# @return [{Class => AttributeMetadata}] this class's owner type => attribute hash
|
184
|
+
def owner_attribute_metadata_hash
|
185
|
+
@oa_hash ||= create_owner_attribute_metadata_hash
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def local_owner_attribute_metadata_hash
|
191
|
+
@local_oa_hash ||= {}
|
192
|
+
end
|
193
|
+
|
194
|
+
# @return [{Class => AttributeMetadata}] a new owner type => attribute hash
|
195
|
+
def create_owner_attribute_metadata_hash
|
196
|
+
local = local_owner_attribute_metadata_hash
|
197
|
+
superclass < Resource ? local.union(superclass.owner_attribute_metadata_hash) : local
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [<AttributeMetadata>] the owner attributes
|
201
|
+
def owner_attribute_metadata_enumerator
|
202
|
+
# Enumerate each owner AttributeMetadata, filtering out nil values.
|
203
|
+
@oa_enum ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_value).filter
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns the attribute which references the owner. The owner attribute is the inverse
|
207
|
+
# of the given owner class inverse attribute, if it exists. Otherwise, the owner
|
208
|
+
# attribute is inferred by #{ResourceInverse#detect_inverse_attribute}.
|
209
|
+
|
210
|
+
# @param klass (see #add_owner)
|
211
|
+
# @param [Symbol] inverse the owner -> dependent attribute
|
212
|
+
# @return [Symbol, nil] this class's owner attribute
|
213
|
+
def detect_owner_attribute(klass, inverse)
|
214
|
+
klass.attribute_metadata(inverse).inverse or detect_inverse_attribute(klass)
|
128
215
|
end
|
129
216
|
end
|
130
217
|
end
|
@@ -96,7 +96,7 @@ module CaRuby
|
|
96
96
|
logger.debug { "Filtered #{qp} #{attribute} and #{awtr} methods with Java Date <-> Ruby Date converter." }
|
97
97
|
end
|
98
98
|
|
99
|
-
# Aliases the methods
|
99
|
+
# Aliases the methods _aliaz_ and _aliaz=_ to _property_ and _property=_, resp.,
|
100
100
|
# where _property_ is the Java property name for the attribute.
|
101
101
|
def alias_attribute_property(aliaz, attribute)
|
102
102
|
# strip the Java reader and writer is/get/set prefix and make a symbol
|
@@ -7,6 +7,21 @@ module CaRuby
|
|
7
7
|
|
8
8
|
protected
|
9
9
|
|
10
|
+
# Infers the inverse of the given attribute declared by this class. A domain attribute is
|
11
|
+
# recognized as an inverse according to the {ResourceInverse#detect_inverse_attribute}
|
12
|
+
# criterion.
|
13
|
+
#
|
14
|
+
# @param [AttributeMetadata] attr_md the attribute to check
|
15
|
+
def infer_attribute_inverse(attr_md)
|
16
|
+
inv = attr_md.type.detect_inverse_attribute(self)
|
17
|
+
if inv then
|
18
|
+
logger.debug { "Detected #{qp} #{attr_md} inverse #{inv.qp}." }
|
19
|
+
set_attribute_inverse(attr_md.to_sym, inv)
|
20
|
+
else
|
21
|
+
logger.debug { "No inverse detected for #{qp} #{attr_md}." }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
10
25
|
# Sets the given bi-directional association attribute's inverse.
|
11
26
|
#
|
12
27
|
# @param [Symbol] attribute the subject attribute
|
@@ -29,13 +44,15 @@ module CaRuby
|
|
29
44
|
raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
|
30
45
|
end
|
31
46
|
|
32
|
-
# if the attribute is
|
33
|
-
#
|
47
|
+
# if the attribute is not declared by this class, then make a new attribute
|
48
|
+
# metadata specialized for this class.
|
34
49
|
unless attr_md.declarer == self then
|
35
50
|
attr_md = attr_md.dup
|
36
51
|
attr_md.declarer = self
|
37
52
|
add_attribute_metadata(attribute, inverse)
|
53
|
+
logger.debug { "Copied #{attr_md.declarer}.#{attribute} to #{qp} with restricted inverse return type #{qp}." }
|
38
54
|
end
|
55
|
+
# Set the inverse in the attribute metadata.
|
39
56
|
attr_md.inverse = inverse
|
40
57
|
|
41
58
|
# If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
|
@@ -57,7 +74,7 @@ module CaRuby
|
|
57
74
|
# or nil if no owner attribute was detected
|
58
75
|
def detect_inverse_attribute(klass)
|
59
76
|
# the candidate attributes which return the referencing type
|
60
|
-
candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type }
|
77
|
+
candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type }
|
61
78
|
attr = detect_inverse_attribute_from_candidates(klass, candidates)
|
62
79
|
if attr then
|
63
80
|
logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
|
@@ -15,6 +15,23 @@ module CaRuby
|
|
15
15
|
include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes
|
16
16
|
|
17
17
|
attr_reader :domain_module
|
18
|
+
|
19
|
+
# JRuby 1.6 alert - if a concrete class implements a constructor, then calling super in an
|
20
|
+
# included module initialize method results in a recursive call back into that initialize.
|
21
|
+
# The work-around is to redefine each domain class new method to call the initalize_content
|
22
|
+
# method instead. All Resource domain classes or included modules must not override
|
23
|
+
# initialize. They can implement initialize_content instead.
|
24
|
+
def self.extended(klass)
|
25
|
+
klass.class_eval do
|
26
|
+
class << self
|
27
|
+
def new(opts=nil)
|
28
|
+
obj = super()
|
29
|
+
obj.merge_attributes(opts) if opts
|
30
|
+
obj
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
18
35
|
|
19
36
|
# Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
|
20
37
|
# the ResourceModule extends the Resource class with ResourceMetadata and calls this
|
@@ -59,15 +76,13 @@ module CaRuby
|
|
59
76
|
|
60
77
|
# Prints this classifier's content to the log.
|
61
78
|
def pretty_print(q)
|
62
|
-
|
63
|
-
|
64
|
-
# KLUDGE - if not inited then bail
|
79
|
+
# KLUDGE - if not inited then bail.
|
80
|
+
# TODO - isolate when this occurs.
|
65
81
|
if @attr_md_hash.nil? then
|
66
82
|
q.text(qp)
|
67
83
|
return
|
68
84
|
end
|
69
85
|
|
70
|
-
|
71
86
|
# the Java property descriptors
|
72
87
|
property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
|
73
88
|
# build a map of relevant display label => attributes
|
@@ -99,6 +114,7 @@ module CaRuby
|
|
99
114
|
"dependent attributes" => dependents_printer,
|
100
115
|
"default values" => defaults
|
101
116
|
}.delete_if { |key, value| value.nil_or_empty? }
|
117
|
+
|
102
118
|
# one indented line per entry, all but the last line ending in a comma
|
103
119
|
content = map.map { |label, value| " #{label}=>#{format_print_value(value)}" }.join(",\n")
|
104
120
|
# print the content to the log
|
@@ -11,9 +11,11 @@ module CaRuby
|
|
11
11
|
[:password, "--password PSWD", "the application login password"],
|
12
12
|
[:host, "--host HOST", "the application host name"],
|
13
13
|
[:port, "--port PORT", "the application port number"],
|
14
|
+
[:classpath, "--classpath PATH", "the application client classpath"],
|
14
15
|
[:database_host, "--database_host HOST", "the database host name"],
|
15
16
|
[:database_type, "--database_type TYPE", "the database type (mysql or oracle)"],
|
16
|
-
[:database_driver, "--database_driver DRIVER", "the database driver"],
|
17
|
+
[:database_driver, "--database_driver DRIVER", "the database driver string"],
|
18
|
+
[:database_driver_class, "--database_driver_class CLASS", "the database driver class name"],
|
17
19
|
[:database_port, "--database_port PORT", Integer, "the database port number"],
|
18
20
|
[:database, "--database NAME", "the database name"],
|
19
21
|
[:database_user, "--database_user USER", "the database login user"],
|
@@ -31,120 +33,98 @@ module CaRuby
|
|
31
33
|
# The properties are read from a property file. See {Properties} for
|
32
34
|
# more information.
|
33
35
|
#
|
34
|
-
# A Java class is imported into Ruby either by directly calling the
|
35
|
-
#
|
36
|
+
# A Java class is imported into Ruby either by directly calling the extended
|
37
|
+
# module {#resource_import} method or on demand by referencing the class name.
|
36
38
|
# Import on demand is induced by a reference to the class, e.g.:
|
37
39
|
# module ClinicalTrials
|
38
|
-
# extend
|
40
|
+
# extend CaRuby::ResourceModule
|
39
41
|
#
|
40
|
-
#
|
41
|
-
# 'org.nci.ctms'
|
42
|
-
# end
|
42
|
+
# @java_package = 'org.nci.ctms'
|
43
43
|
# ...
|
44
44
|
# enables references by name to a +ClinicalTrials+ Ruby class wrapper of a
|
45
45
|
# +org.nci.ctms+ Java class without an import statement, e.g.:
|
46
46
|
# ClinicalTrials::Participant.new
|
47
47
|
# without defining the +Participant+ Ruby class.
|
48
48
|
module ResourceModule
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# add superclass if necessary
|
55
|
-
unless @rsc_classes.include?(klass.superclass) or klass.superclass == Java::JavaLang::Object then
|
56
|
-
# the domain module includes the superclass on demand
|
57
|
-
const_get(klass.superclass.name[/\w+$/].to_sym)
|
58
|
-
end
|
59
|
-
ResourceMetadata.extend_class(klass, self)
|
60
|
-
@rsc_classes << klass
|
49
|
+
# Loads the {#access_properties} and adds the path property items to the Java classpath.
|
50
|
+
# @param [Module] mod the module to extend
|
51
|
+
def self.extended(mod)
|
52
|
+
super
|
53
|
+
mod.ensure_classpath_defined
|
61
54
|
end
|
62
55
|
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# @return [{Symbol => Object}] the caBIG application access properties
|
69
|
-
# @see #load_access_properties
|
70
|
-
def access_properties
|
71
|
-
@rsc_props ||= load_access_properties
|
72
|
-
end
|
73
|
-
|
74
|
-
# Loads the application start-up properties in the given file path.
|
75
|
-
# The default file path is a period followed by the lower-case application name in the home directory,
|
76
|
-
# e.g. +~/.clincaltrials+.
|
56
|
+
# Loads the application start-up properties on demand. The properties are defined in the properties
|
57
|
+
# file or as environment variables.
|
58
|
+
# The properties file path is a period followed by the lower-case application name in the home directory,
|
59
|
+
# e.g. +~/.clincaltrials+ for the +ClinicalTrials+ application.
|
77
60
|
#
|
78
61
|
# The property file format is a series of property definitions in the form _property_: _value_.
|
79
62
|
# The supported properties include the following:
|
80
|
-
# * +
|
81
|
-
# * +
|
63
|
+
# * +host+ - the application server host (default +localhost+)
|
64
|
+
# * +port+ - the application server port (default +8080+)
|
65
|
+
# * +user+ - the application server login
|
82
66
|
# * +password+ - the application service password
|
67
|
+
# * +path+ or +classpath+ - the application client Java directories
|
83
68
|
# * +database+ - the application database name
|
84
69
|
# * +database_user+ - the application database connection userid
|
85
70
|
# * +database_password+ - the application database connection password
|
86
|
-
# *
|
71
|
+
# * +database_host+ - the application database connection host (default +localhost+)
|
87
72
|
# * +database_type+ - the application database type, + mysql+ or +oracle+ (default +mysql+)
|
88
73
|
# * +database_driver+ - the application database connection driver (default is the database type default)
|
89
|
-
# * +database_port+ - the application database connection port
|
74
|
+
# * +database_port+ - the application database connection port (default is the database type default)
|
90
75
|
#
|
91
76
|
# The +path+ value is one or more directories separated by a semi-colon(;) or colon (:)
|
92
77
|
# Each path directory and all jar files within the directory are added to the caRuby execution
|
93
78
|
# Java classpath.
|
94
79
|
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
props[opt] = value
|
133
|
-
logger.info("Set application property #{opt} from environment variable #{ev}.")
|
80
|
+
# Each property has an environment variable counterpart given by
|
81
|
+
#
|
82
|
+
# @return [{Symbol => Object}] the caBIG application access properties
|
83
|
+
def access_properties
|
84
|
+
@rsc_props ||= load_access_properties
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ensures that the application client classpath is defined. The classpath is defined
|
88
|
+
# in the {#access_properties}. This method is called when a module extends this
|
89
|
+
# ResourceModule, before any application Java domain class is imported into JRuby.
|
90
|
+
def ensure_classpath_defined
|
91
|
+
# Loading the access properties on demand sets the classpath.
|
92
|
+
access_properties
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Module] the resource mix-in module (default {Resouce})
|
96
|
+
def mixin
|
97
|
+
@mixin || Resource
|
98
|
+
end
|
99
|
+
|
100
|
+
# Adds the given class to this ResourceModule. The class is extended with ResourceMetadata methods.
|
101
|
+
#
|
102
|
+
# @param [Class] the {Resource} class to add
|
103
|
+
def add_class(klass)
|
104
|
+
logger.debug { "Adding #{klass.java_class.name} to #{qp}..." }
|
105
|
+
@rsc_classes ||= Set.new
|
106
|
+
# add superclass if necessary
|
107
|
+
sc = klass.superclass
|
108
|
+
unless @rsc_classes.include?(sc) then
|
109
|
+
# the domain module includes the superclass on demand
|
110
|
+
sc_pkg, sc_sym = Java.split_class_name(sc)
|
111
|
+
if const_defined?(sc_sym) or sc_pkg == @java_package then
|
112
|
+
const_get(sc_sym)
|
113
|
+
else
|
114
|
+
mod = mixin
|
115
|
+
klass.class_eval { include mod }
|
116
|
+
end
|
134
117
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
Java.add_path(path) if path
|
140
|
-
|
141
|
-
props
|
118
|
+
ResourceMetadata.extend_class(klass, self)
|
119
|
+
@rsc_classes << klass
|
120
|
+
class_added(klass)
|
121
|
+
logger.debug { "#{klass.java_class.name} added to #{qp}." }
|
142
122
|
end
|
143
123
|
|
144
|
-
#
|
124
|
+
# Auto-loads the Ruby source files in the given directory.
|
125
|
+
#
|
126
|
+
# @param [String] dir the source directory
|
145
127
|
def load_dir(dir)
|
146
|
-
# load the properties on demand
|
147
|
-
load_access_properties if @rsc_props.nil?
|
148
128
|
# the domain class definitions
|
149
129
|
sources = Dir.glob(File.join(dir, "*.rb"))
|
150
130
|
|
@@ -171,21 +151,34 @@ module CaRuby
|
|
171
151
|
end
|
172
152
|
end
|
173
153
|
|
174
|
-
|
154
|
+
# @param [Class, String] class_or_name the class to import into this module
|
155
|
+
# @return [Class] the imported class
|
156
|
+
def java_import(class_or_name)
|
175
157
|
# JRuby 1.4.x does not support a class argument
|
176
|
-
Class ===
|
158
|
+
Class === class_or_name ? super(class_or_name.java_class.name) : super
|
159
|
+
end
|
160
|
+
|
161
|
+
# @param [Class, String] class_or_name the class to import into this module
|
162
|
+
# @return [Class] the imported {Resource} class
|
163
|
+
def resource_import(class_or_name)
|
164
|
+
klass = java_import(class_or_name)
|
165
|
+
mod = mixin
|
166
|
+
klass.instance_eval { include mod }
|
167
|
+
add_class(klass)
|
168
|
+
klass
|
177
169
|
end
|
178
170
|
|
179
|
-
#
|
171
|
+
# Imports a class constant on demand. See the class documentation for details.
|
180
172
|
#
|
181
173
|
# @param [Symbol] symbol the missing constant
|
182
174
|
def const_missing(symbol)
|
183
175
|
autoload?(symbol) ? super : import_domain_class(symbol)
|
184
176
|
end
|
185
177
|
|
186
|
-
#
|
187
|
-
|
188
|
-
|
178
|
+
# @param [String] the class name to check
|
179
|
+
# @eturn [Class, nil] the domain class for the class name, or nil if none in this module
|
180
|
+
def domain_type_with_name(name)
|
181
|
+
pkg, base = Java.split_class_name(name)
|
189
182
|
return unless pkg.nil? or pkg == @java_package
|
190
183
|
begin
|
191
184
|
type = const_get(base)
|
@@ -200,6 +193,77 @@ module CaRuby
|
|
200
193
|
|
201
194
|
private
|
202
195
|
|
196
|
+
# Callback invoked after the given domain class is added to this domain module.
|
197
|
+
#
|
198
|
+
# @param [Class] klass the class that was added
|
199
|
+
def class_added(klass); end
|
200
|
+
|
201
|
+
# Loads the application start-up properties in the given file path.
|
202
|
+
#
|
203
|
+
# @return (see #access_properties)
|
204
|
+
def load_access_properties
|
205
|
+
# the properties file
|
206
|
+
file = default_properties_file
|
207
|
+
# the access properties
|
208
|
+
props = file && File.exists?(file) ? load_properties_file(file) : {}
|
209
|
+
# Look for environment overrides preceded by the uppercase module name,
|
210
|
+
# e.g. CATISSUE_USER for the CaTissue module.
|
211
|
+
load_environment_properties(props)
|
212
|
+
|
213
|
+
# load the Java application jar path
|
214
|
+
path = props[:classpath] || props[:path]
|
215
|
+
if path then
|
216
|
+
logger.info("Defining application classpath #{path}...")
|
217
|
+
Java.add_path(path)
|
218
|
+
end
|
219
|
+
|
220
|
+
props
|
221
|
+
end
|
222
|
+
|
223
|
+
def load_properties_file(file)
|
224
|
+
props = {}
|
225
|
+
logger.info("Loading application properties from #{file}...")
|
226
|
+
File.open(file).map do |line|
|
227
|
+
# match the tolerant property definition
|
228
|
+
match = PROP_DEF_REGEX.match(line.chomp) || next
|
229
|
+
# the property [name, value] tokens
|
230
|
+
tokens = match.captures
|
231
|
+
pname = tokens.first.to_sym
|
232
|
+
# path is deprecated
|
233
|
+
name = pname == :path ? :classpath : pname
|
234
|
+
value = tokens.last
|
235
|
+
# capture the property
|
236
|
+
props[name] = value
|
237
|
+
end
|
238
|
+
props
|
239
|
+
end
|
240
|
+
|
241
|
+
def load_environment_properties(props)
|
242
|
+
ACCESS_OPTS.each do |spec|
|
243
|
+
# the access option symbol is the first specification item
|
244
|
+
opt = spec[0]
|
245
|
+
# the envvar value
|
246
|
+
value = environment_property(opt) || next
|
247
|
+
# override the file property with the envar value
|
248
|
+
props[opt] = value
|
249
|
+
logger.info("Set application property #{opt} from environment variable #{ev}.")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# @param [Symbol] opt the property symbol, e.g. :user
|
254
|
+
# @return [String, nil] the value of the corresponding environment variable, e.g. +CATISSUE_USER+
|
255
|
+
def environment_property(opt)
|
256
|
+
@env_prefix ||= name.gsub('::', '_').upcase
|
257
|
+
ev = "#{@env_prefix}_#{opt.to_s.upcase}"
|
258
|
+
value = ENV[ev]
|
259
|
+
# If no classpath envvar, then try the deprecated path envvar.
|
260
|
+
if value.nil? and opt == :classpath then
|
261
|
+
environment_property(:path)
|
262
|
+
else
|
263
|
+
value
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
203
267
|
# Imports the domain Java class with specified class name_or_sym.
|
204
268
|
# This method enables the domain class extensions for storing and retrieving domain objects.
|
205
269
|
# The class is added to this module.
|
@@ -222,14 +286,10 @@ module CaRuby
|
|
222
286
|
#
|
223
287
|
# The optional aliases argument consists of additional alias => standard attribute associations.
|
224
288
|
# The optional owner_attr argument is a non-Java annotation owner attribute.
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
else
|
230
|
-
symbol = name_or_sym.to_sym
|
231
|
-
name = [@java_package, name].join('.')
|
232
|
-
end
|
289
|
+
#
|
290
|
+
# @param [Symbol] symbol the class symbol
|
291
|
+
# @param [String, nil] pkg the Java class package name, or nil for the default module package
|
292
|
+
def import_domain_class(symbol, pkg=nil)
|
233
293
|
# check if the class is already defined
|
234
294
|
if const_defined?(symbol) then
|
235
295
|
klass = const_get(symbol)
|
@@ -239,35 +299,50 @@ module CaRuby
|
|
239
299
|
end
|
240
300
|
|
241
301
|
# import the Java class
|
242
|
-
|
302
|
+
pkg ||= @java_package
|
303
|
+
name = [pkg, symbol.to_s].join('.')
|
304
|
+
logger.debug { "Detecting whether #{symbol} is a #{pkg} Java class..." }
|
305
|
+
# Push each imported class onto a stack. When all referenced classes are imported,
|
306
|
+
# each class on the stack is post-initialized and the class structure is printed to
|
307
|
+
# the log.
|
308
|
+
@import_stack ||= []
|
309
|
+
@import_stack.push(symbol)
|
243
310
|
begin
|
244
|
-
|
245
|
-
rescue Exception
|
246
|
-
|
311
|
+
resource_import(name)
|
312
|
+
rescue Exception
|
313
|
+
@import_stack.pop
|
314
|
+
if symbol.to_s =~ /Annotation/ then raise end
|
315
|
+
raise JavaIncludeError.new("#{symbol} is not a #{qp} Java class - #{$!}")
|
247
316
|
end
|
248
317
|
|
249
318
|
# the imported Java class is registered as a constant in this module
|
250
319
|
klass = const_get(symbol)
|
251
|
-
# the Resource import stack
|
252
|
-
@import_stack ||= []
|
253
|
-
@import_stack.push klass
|
254
|
-
# include the Resource mixin in the imported class
|
255
|
-
inc = "include #{mixin}"
|
256
|
-
klass.instance_eval(inc)
|
257
|
-
|
258
320
|
# if we are back to top of the stack, then print the imported Resources
|
259
|
-
if
|
321
|
+
if symbol == @import_stack.first then
|
260
322
|
# a referenced class could be imported on demand in the course of printing a referencing class;
|
261
323
|
# the referenced class is then pushed onto the stack. thus, the stack can grow during the
|
262
324
|
# course of printing, but each imported class is consumed and printed in the end.
|
263
325
|
until @import_stack.empty? do
|
264
|
-
|
265
|
-
|
326
|
+
# the class constant
|
327
|
+
sym = @import_stack.pop
|
328
|
+
# the imported class
|
329
|
+
kls = const_get(sym)
|
330
|
+
# invoke the call-back
|
331
|
+
imported(kls)
|
332
|
+
# print the class structure to the log
|
333
|
+
logger.info(kls.pp_s)
|
266
334
|
end
|
267
335
|
end
|
336
|
+
|
268
337
|
klass
|
269
338
|
end
|
270
339
|
|
340
|
+
# Call-back to perform post-import actions. This method is called after the
|
341
|
+
# given class and each of its referenced domain classes are introspected.
|
342
|
+
#
|
343
|
+
# @param [Class] the imported class
|
344
|
+
def imported(klass); end
|
345
|
+
|
271
346
|
# The property/value matcher, e.g.:
|
272
347
|
# host: jacardi
|
273
348
|
# host = jacardi
|
@@ -287,18 +362,9 @@ module CaRuby
|
|
287
362
|
if File.exists?(file) then
|
288
363
|
file
|
289
364
|
else
|
290
|
-
logger.
|
365
|
+
logger.debug { "Default #{name} application property file not found: #{file}." }
|
291
366
|
nil
|
292
367
|
end
|
293
368
|
end
|
294
|
-
|
295
|
-
# @return [(String, Symbol)] the [package prefix, base class symbol] pair
|
296
|
-
def split_class_name(class_name)
|
297
|
-
# the package prefix, including the period
|
298
|
-
package = Java.java_package_name(class_name)
|
299
|
-
# remove the package and base class name
|
300
|
-
base = package.nil? ? class_name : class_name[package.length + 1..-1]
|
301
|
-
[package, base.to_sym]
|
302
|
-
end
|
303
369
|
end
|
304
370
|
end
|