caruby-core 1.4.7 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|