caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,225 +0,0 @@
|
|
1
|
-
require 'caruby/util/validation'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
module Domain
|
5
|
-
# Metadata mix-in to capture Resource dependency.
|
6
|
-
module Dependency
|
7
|
-
|
8
|
-
attr_reader :owners, :owner_attributes
|
9
|
-
|
10
|
-
# Returns the most specific attribute which references the dependent type, or nil if none.
|
11
|
-
# If the given class can be returned by more than dependent attribute, then the attribute
|
12
|
-
# is chosen whose return type most closely matches the given class.
|
13
|
-
#
|
14
|
-
# @param [Class] klass the dependent type
|
15
|
-
# @return [Symbol, nil] the dependent reference attribute, or nil if none
|
16
|
-
def dependent_attribute(klass)
|
17
|
-
dependent_attributes.inject(nil) do |best, attr|
|
18
|
-
type = domain_type(attr)
|
19
|
-
# If the attribute can return the klass then the return type is a candidate.
|
20
|
-
# In that case, the klass replaces the best candidate if it is more specific than
|
21
|
-
# the best candidate so far.
|
22
|
-
klass <= type ? (best && best < type ? best : type) : best
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Adds the given attribute as a dependent.
|
27
|
-
#
|
28
|
-
# Supported flags include the following:
|
29
|
-
# * :logical - the dependency relation is not cascaded by the application
|
30
|
-
# * :autogenerated - a dependent can be created by the application as a side-effect of creating the owner
|
31
|
-
# * :disjoint - the dependent owner has more than one owner attribute, but only one owner instance
|
32
|
-
#
|
33
|
-
# If the attribute inverse is not a collection, then the attribute writer
|
34
|
-
# is modified to delegate to the dependent owner writer. This enforces
|
35
|
-
# referential integrity by ensuring that the following post-condition holds:
|
36
|
-
# * _owner_._attribute_._inverse_ == _owner_
|
37
|
-
# where:
|
38
|
-
# * _owner_ is an instance this attribute's declaring class
|
39
|
-
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
40
|
-
#
|
41
|
-
# @param [Symbol] attribute the dependent to add
|
42
|
-
# @param [<Symbol>] flags the attribute qualifier flags
|
43
|
-
def add_dependent_attribute(attribute, *flags)
|
44
|
-
attr_md = attribute_metadata(attribute)
|
45
|
-
flags << :dependent unless flags.include?(:dependent)
|
46
|
-
attr_md.qualify(*flags)
|
47
|
-
inverse = attr_md.inverse
|
48
|
-
inv_type = attr_md.type
|
49
|
-
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
|
50
|
-
# Child.add_owner(Parent, :children, :parent)
|
51
|
-
inv_type.add_owner(self, attribute, inverse)
|
52
|
-
end
|
53
|
-
|
54
|
-
# @return [Boolean] whether this class depends on an owner
|
55
|
-
def dependent?
|
56
|
-
not owners.empty?
|
57
|
-
end
|
58
|
-
|
59
|
-
# @return [Boolean] whether this class has an owner which cascades save operations to this dependent
|
60
|
-
def cascaded_dependent?
|
61
|
-
owner_attribute_metadata_enumerator.any? { |attr_md| attr_md.inverse_metadata.cascaded? }
|
62
|
-
end
|
63
|
-
|
64
|
-
# @return [Boolean] whether this class depends the given other class
|
65
|
-
def depends_on?(other)
|
66
|
-
owners.detect { |owner| owner === other }
|
67
|
-
end
|
68
|
-
|
69
|
-
# @param [Class] klass the dependent type
|
70
|
-
# @return [Symbol, nil] the attribute which references the dependent type, or nil if none
|
71
|
-
def dependent_attribute(klass)
|
72
|
-
type = dependent_attributes.detect_with_metadata { |attr_md| attr_md.type == klass }
|
73
|
-
return type if type
|
74
|
-
dependent_attribute(klass.superclass) if klass.superclass < Resource
|
75
|
-
end
|
76
|
-
|
77
|
-
# @return [<Symbol>] this class's owner attributes
|
78
|
-
def owner_attributes
|
79
|
-
@oattrs ||= owner_attribute_metadata_enumerator.transform { |attr_md| attr_md.to_sym }
|
80
|
-
end
|
81
|
-
|
82
|
-
# @return [Boolean] whether this {Resource} class is dependent and reference its owners
|
83
|
-
def bidirectional_dependent?
|
84
|
-
dependent? and not owner_attributes.empty?
|
85
|
-
end
|
86
|
-
|
87
|
-
# @return [<Class>] this class's dependent types
|
88
|
-
def dependents
|
89
|
-
dependent_attributes.wrap { |attr| attr.type }
|
90
|
-
end
|
91
|
-
|
92
|
-
# @return [<Class>] this class's owner types
|
93
|
-
def owners
|
94
|
-
@owners ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_key)
|
95
|
-
end
|
96
|
-
|
97
|
-
# @return [Attribute, nil] the sole owner attribute metadata of this class, or nil if there
|
98
|
-
# is not exactly one owner
|
99
|
-
def owner_attribute_metadata
|
100
|
-
attr_mds = owner_attribute_metadata_enumerator
|
101
|
-
attr_mds.first if attr_mds.size == 1
|
102
|
-
end
|
103
|
-
|
104
|
-
# @return [Symbol, nil] the sole owner attribute of this class, or nil if there
|
105
|
-
# is not exactly one owner
|
106
|
-
def owner_attribute
|
107
|
-
attr_md = owner_attribute_metadata || return
|
108
|
-
attr_md.to_sym
|
109
|
-
end
|
110
|
-
|
111
|
-
# @return [Class, nil] the sole owner type of this class, or nil if there
|
112
|
-
# is not exactly one owner
|
113
|
-
def owner_type
|
114
|
-
attr_md = owner_attribute_metadata || return
|
115
|
-
attr_md.type
|
116
|
-
end
|
117
|
-
|
118
|
-
protected
|
119
|
-
|
120
|
-
# Adds the given owner class to this dependent class.
|
121
|
-
# This method must be called before any dependent attribute is accessed.
|
122
|
-
# If the attribute is given, then the attribute inverse is set.
|
123
|
-
# Otherwise, if there is not already an owner attribute, then a new owner attribute is created.
|
124
|
-
# The name of the new attribute is the lower-case demodulized owner class name.
|
125
|
-
#
|
126
|
-
# @param [Class] the owner class
|
127
|
-
# @param [Symbol] inverse the owner -> dependent attribute
|
128
|
-
# @param [Symbol, nil] attribute the dependent -> owner attribute, if known
|
129
|
-
# @raise [ValidationError] if the inverse is nil
|
130
|
-
def add_owner(klass, inverse, attribute=nil)
|
131
|
-
if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
|
132
|
-
logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute}#{' inverse ' + inverse.to_s if inverse}..." }
|
133
|
-
if @owner_attr_hash then
|
134
|
-
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
135
|
-
end
|
136
|
-
|
137
|
-
# detect the owner attribute, if necessary
|
138
|
-
attribute ||= detect_owner_attribute(klass, inverse)
|
139
|
-
attr_md = attribute_metadata(attribute) if attribute
|
140
|
-
# Add the owner class => attribute entry.
|
141
|
-
# The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
|
142
|
-
# references this class via a dependency attribute but there is no inverse owner attribute.
|
143
|
-
local_owner_attribute_metadata_hash[klass] = attr_md
|
144
|
-
# If the dependency is unidirectional, then our job is done.
|
145
|
-
return if attribute.nil?
|
146
|
-
|
147
|
-
# set the inverse if necessary
|
148
|
-
unless attr_md.inverse then
|
149
|
-
set_attribute_inverse(attribute, inverse)
|
150
|
-
end
|
151
|
-
# set the owner flag if necessary
|
152
|
-
unless attr_md.owner? then attr_md.qualify(:owner) end
|
153
|
-
# Redefine the writer method to warn when changing the owner
|
154
|
-
rdr, wtr = attr_md.accessors
|
155
|
-
logger.debug { "Injecting owner change warning into #{qp}.#{attribute} writer method #{wtr}..." }
|
156
|
-
redefine_method(wtr) do |old_wtr|
|
157
|
-
lambda do |ref|
|
158
|
-
prev = send(rdr)
|
159
|
-
if prev and prev != ref then
|
160
|
-
if ref.nil? then
|
161
|
-
logger.warn("Unsetting the #{self} owner #{attribute} #{prev}.")
|
162
|
-
elsif ref.identifier != prev.identifier then
|
163
|
-
logger.warn("Resetting the #{self} owner #{attribute} from #{prev} to #{ref}.")
|
164
|
-
end
|
165
|
-
end
|
166
|
-
send(old_wtr, ref)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
# Adds the given attribute as an owner. This method is called when a new attribute is added that
|
172
|
-
# references an existing owner.
|
173
|
-
#
|
174
|
-
# @param [Symbol] attribute the owner attribute
|
175
|
-
def add_owner_attribute(attribute)
|
176
|
-
attr_md = attribute_metadata(attribute)
|
177
|
-
otype = attr_md.type
|
178
|
-
hash = local_owner_attribute_metadata_hash
|
179
|
-
if hash.include?(otype) then
|
180
|
-
oattr = hash[otype]
|
181
|
-
unless oattr.nil? then
|
182
|
-
raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oattr}")
|
183
|
-
end
|
184
|
-
hash[otype] = attr_md
|
185
|
-
else
|
186
|
-
add_owner(otype, attr_md.inverse, attribute)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# @return [{Class => Attribute}] this class's owner type => attribute hash
|
191
|
-
def owner_attribute_metadata_hash
|
192
|
-
@oa_hash ||= create_owner_attribute_metadata_hash
|
193
|
-
end
|
194
|
-
|
195
|
-
private
|
196
|
-
|
197
|
-
def local_owner_attribute_metadata_hash
|
198
|
-
@local_oa_hash ||= {}
|
199
|
-
end
|
200
|
-
|
201
|
-
# @return [{Class => Attribute}] a new owner type => attribute hash
|
202
|
-
def create_owner_attribute_metadata_hash
|
203
|
-
local = local_owner_attribute_metadata_hash
|
204
|
-
superclass < Resource ? local.union(superclass.owner_attribute_metadata_hash) : local
|
205
|
-
end
|
206
|
-
|
207
|
-
# @return [<Attribute>] the owner attributes
|
208
|
-
def owner_attribute_metadata_enumerator
|
209
|
-
# Enumerate each owner Attribute, filtering out nil values.
|
210
|
-
@oa_enum ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_value).filter
|
211
|
-
end
|
212
|
-
|
213
|
-
# Returns the attribute which references the owner. The owner attribute is the inverse
|
214
|
-
# of the given owner class inverse attribute, if it exists. Otherwise, the owner
|
215
|
-
# attribute is inferred by #{Inverse#detect_inverse_attribute}.
|
216
|
-
|
217
|
-
# @param klass (see #add_owner)
|
218
|
-
# @param [Symbol] inverse the owner -> dependent attribute
|
219
|
-
# @return [Symbol, nil] this class's owner attribute
|
220
|
-
def detect_owner_attribute(klass, inverse)
|
221
|
-
klass.attribute_metadata(inverse).inverse or detect_inverse_attribute(klass)
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module CaRuby
|
2
|
-
# Mix-in for Java classes which have an +id+ property.
|
3
|
-
# Since +id+ is a reserved Ruby method, this mix-in defines an +identifier+ attribute
|
4
|
-
# which fronts the +id+ property.
|
5
|
-
module IdAlias
|
6
|
-
# Returns the identifier.
|
7
|
-
# This method delegates to the Java +id+ property reader method.
|
8
|
-
#
|
9
|
-
# @return [Integer] the identifier value
|
10
|
-
def identifier
|
11
|
-
getId
|
12
|
-
end
|
13
|
-
|
14
|
-
# Sets the identifier to the given value.
|
15
|
-
# This method delegates to the Java +id+ property writer method.
|
16
|
-
#
|
17
|
-
# @param [Integer] value the value to set
|
18
|
-
def identifier=(value)
|
19
|
-
setId(value)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,183 +0,0 @@
|
|
1
|
-
require 'caruby/domain/metadata'
|
2
|
-
require 'caruby/resource'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
module Domain
|
6
|
-
# Importer extends a {Module} with Java class import support.
|
7
|
-
#
|
8
|
-
# A Java class is imported into JRuby on demand by referencing the class name.
|
9
|
-
# Import on demand is induced by a reference to the class, e.g., given the
|
10
|
-
# following domain resource module definition:
|
11
|
-
# module ClinicalTrials
|
12
|
-
# module Resource
|
13
|
-
# ...
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# CaRuby::Domain.extend_module(self, Resource, 'org.nci.ctms')
|
17
|
-
# then the first reference by name to +ClinicalTrials::Subject+
|
18
|
-
# imports the Java class +org.nci.ctms.Subject+ into the JRuby class wrapper
|
19
|
-
# +ClinicalTrials::Subject+. The +ClinicalTrials::Resource+ module is included
|
20
|
-
# in +ClinicalTrials::Subject+ and the Java property meta-data is introspected
|
21
|
-
# into {Attributes}.
|
22
|
-
module Importer
|
23
|
-
# Extends the given module with Java class meta-data import support.
|
24
|
-
#
|
25
|
-
# @param [Module] mod the module to extend
|
26
|
-
# @param [{Symbol => Object}] opts the extension options
|
27
|
-
# @option opts (see #configure)
|
28
|
-
def self.extend_module(mod, opts)
|
29
|
-
mod.extend(self).configure_importer(opts)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Imports a Java class constant on demand. If the class does not already
|
33
|
-
# include this module's mixin, then the mixin is included in the class.
|
34
|
-
#
|
35
|
-
# @param [Symbol] symbol the missing constant
|
36
|
-
# @return [Class] the imported class
|
37
|
-
# @raise [NameError] if the symbol is not an importable Java class
|
38
|
-
def const_missing(symbol)
|
39
|
-
logger.debug { "Detecting whether #{symbol} is a #{@pkg} Java class..." }
|
40
|
-
# Append the symbol to the package to make the Java class name.
|
41
|
-
begin
|
42
|
-
klass = java_import "#{@pkg}.#{symbol}"
|
43
|
-
resource_import(klass)
|
44
|
-
rescue NameError
|
45
|
-
logger.debug { "#{symbol} is not recognized as a #{@pkg} Java class - #{$!}\n#{caller.qp}." }
|
46
|
-
super
|
47
|
-
end
|
48
|
-
logger.info(klass.pp_s)
|
49
|
-
klass
|
50
|
-
end
|
51
|
-
|
52
|
-
# Imports the given Java class and introspects the {Metadata}.
|
53
|
-
# The Java class is assumed to be defined in this module's package.
|
54
|
-
# This module's mixin is added to the class.
|
55
|
-
#
|
56
|
-
# @param [Class] klass the source directory
|
57
|
-
# @raise [NameError] if the symbol does not correspond to a Java class
|
58
|
-
# in this module's package
|
59
|
-
def resource_import(klass)
|
60
|
-
# Add the superclass metadata, if necessary.
|
61
|
-
sc = klass.superclass
|
62
|
-
unless sc < @mixin or klass.parent_module != sc.parent_module then
|
63
|
-
const_get(sc.name.demodulize)
|
64
|
-
end
|
65
|
-
java_import(klass)
|
66
|
-
ensure_metadata_introspected(klass)
|
67
|
-
klass
|
68
|
-
end
|
69
|
-
|
70
|
-
# @param [Class, String] class_or_name the class to import into this module
|
71
|
-
# @return [Class] the imported class
|
72
|
-
def java_import(class_or_name)
|
73
|
-
# JRuby 1.4.x does not support a class argument
|
74
|
-
begin
|
75
|
-
Class === class_or_name ? super(class_or_name.java_class.name) : super
|
76
|
-
rescue Exception
|
77
|
-
raise JavaImportError.new("#{class_or_name} is not a Java class - #{$!}")
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Configures this importer with the given options. This method is intended for use by the
|
82
|
-
# +extend_module+ method.
|
83
|
-
#
|
84
|
-
# @param [{Symbol => Object}] opts the extension options
|
85
|
-
# @option opts [String] :package the required Java package name
|
86
|
-
# @option opts [Module, Proc] :metadata the optional {Metadata} extension module or proc (default {Metadata})
|
87
|
-
# @option opts [Module] :mixin the optional mix-in module (default {Resource})
|
88
|
-
# @option opts [String] :directory the optional directory of source class definitions to load
|
89
|
-
def configure_importer(opts)
|
90
|
-
@pkg = opts[:package]
|
91
|
-
if @pkg.nil? then raise ArgumentError.new("Required domain package option not found") end
|
92
|
-
@metadata = opts[:metadata] || Metadata
|
93
|
-
@mixin = opts[:mixin] || Resource
|
94
|
-
@introspected = Set.new
|
95
|
-
dir = opts[:directory]
|
96
|
-
load_dir(dir) if dir
|
97
|
-
end
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
|
-
# Enables the given class {Metadata} if necessary.
|
102
|
-
#
|
103
|
-
# @param [Class] klass the class to enable
|
104
|
-
def ensure_metadata_introspected(klass)
|
105
|
-
add_metadata(klass) unless @introspected.include?(klass)
|
106
|
-
end
|
107
|
-
|
108
|
-
# Enables the given class meta-data.
|
109
|
-
#
|
110
|
-
# @param [Class] klass the class to enable
|
111
|
-
def add_metadata(klass)
|
112
|
-
# Mark the class as introspected. Do this first to preclude a recursive loop back
|
113
|
-
# into this method when the references are introspected in add_metadata.
|
114
|
-
@introspected << klass
|
115
|
-
# the package module
|
116
|
-
mod = klass.parent_module
|
117
|
-
# Add the superclass metadata, if necessary.
|
118
|
-
sc = klass.superclass
|
119
|
-
unless @introspected.include?(sc) or sc.parent_module != mod then
|
120
|
-
resource_import(sc)
|
121
|
-
end
|
122
|
-
# Include the mixin.
|
123
|
-
unless klass < @mixin then
|
124
|
-
mixin = @mixin
|
125
|
-
klass.class_eval { include mixin }
|
126
|
-
end
|
127
|
-
# Add the class metadata.
|
128
|
-
case @metadata
|
129
|
-
when Module then klass.extend(@metadata)
|
130
|
-
when Proc then @metadata.call(klass)
|
131
|
-
else raise MetadataError.new("#{self} metadata is neither a class nor a proc: #{@metadata.qp}")
|
132
|
-
end
|
133
|
-
klass.domain_module = self
|
134
|
-
# Add referenced domain class metadata as necessary.
|
135
|
-
klass.each_attribute_metadata do |attr_md|
|
136
|
-
ref = attr_md.type
|
137
|
-
if ref.nil? then raise MetadataError.new("#{self} #{attr_md} domain type is unknown.") end
|
138
|
-
unless @introspected.include?(ref) or ref.parent_module != mod then
|
139
|
-
logger.debug { "Adding #{qp} #{attr_md} reference #{ref.qp} metadata..." }
|
140
|
-
resource_import(ref)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Loads the Ruby source files in the given directory.
|
146
|
-
#
|
147
|
-
# @param [String] dir the source directory
|
148
|
-
def load_dir(dir)
|
149
|
-
# Auto-load the files on demand.
|
150
|
-
syms = autoload_dir(dir)
|
151
|
-
# Load each file on demand.
|
152
|
-
syms.each do |sym|
|
153
|
-
klass = const_get(sym)
|
154
|
-
logger.info(klass.pp_s)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# Auto-loads the Ruby source files in the given directory.
|
159
|
-
#
|
160
|
-
# @param [String] dir the source directory
|
161
|
-
# @return [<Symbol>] the class constants that will be loaded
|
162
|
-
def autoload_dir(dir)
|
163
|
-
# the domain class definitions
|
164
|
-
srcs = Dir.glob(File.join(dir, "*.rb"))
|
165
|
-
# autoload the domain classes to ensure that definitions are picked up on demand in class hierarchy order
|
166
|
-
srcs.map do |file|
|
167
|
-
base_name = File.basename(file, ".rb")
|
168
|
-
sym = base_name.camelize.to_sym
|
169
|
-
# JRuby autoload of classes defined in a submodule of a Java wrapper class is not supported.
|
170
|
-
# However, this only occurs with the caTissue Specimen Pathology annotation class definitions,
|
171
|
-
# not the caTissue Participant or SCG annotations. TODO - confirm, isolate and report.
|
172
|
-
# Work-around is to require the files instead.
|
173
|
-
if name[/^Java::/] then
|
174
|
-
require file
|
175
|
-
else
|
176
|
-
autoload(sym, file)
|
177
|
-
end
|
178
|
-
sym
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
@@ -1,176 +0,0 @@
|
|
1
|
-
require 'caruby/util/module'
|
2
|
-
require 'caruby/import/java'
|
3
|
-
require 'caruby/domain/java_attribute'
|
4
|
-
|
5
|
-
module CaRuby
|
6
|
-
module Domain
|
7
|
-
# Meta-data mix-in to infer attribute meta-data from Java properties.
|
8
|
-
module Introspection
|
9
|
-
|
10
|
-
protected
|
11
|
-
|
12
|
-
# @return [Boolean] whether this {Resource} class meta-data has been introspected
|
13
|
-
def introspected?
|
14
|
-
# initialization sets the attribute => metadata hash
|
15
|
-
not @attr_md_hash.nil?
|
16
|
-
end
|
17
|
-
|
18
|
-
# Defines the Java property attribute and standard attribute methods, e.g.
|
19
|
-
# +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
|
20
|
-
# with an additional reader alias, e.g. +available?+ for +is_available+.
|
21
|
-
#
|
22
|
-
# Each Java property attribute delegates to the Java property getter and setter.
|
23
|
-
# Each standard attribute delegates to the Java property attribute.
|
24
|
-
# Redefining these methods results in a call to the redefined method.
|
25
|
-
# This contrasts with a Ruby alias, where the alias remains bound to the
|
26
|
-
# original method body.
|
27
|
-
def introspect
|
28
|
-
# the module corresponding to the Java package of this class
|
29
|
-
mod = parent_module
|
30
|
-
# Set up the attribute data structures; delegates to Attributes.
|
31
|
-
init_attributes
|
32
|
-
logger.debug { "Introspecting #{qp} metadata..." }
|
33
|
-
# The Java properties defined by this class with both a read and a write method.
|
34
|
-
pds = java_properties(false)
|
35
|
-
# Define the standard Java attribute methods.
|
36
|
-
pds.each { |pd| define_java_attribute(pd) }
|
37
|
-
logger.debug { "Introspection of #{qp} metadata complete." }
|
38
|
-
self
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
# Defines the Java property attribute and standard attribute methods, e.g.
|
44
|
-
# +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
|
45
|
-
# with an additional reader alias, e.g. +available?+ for +is_available+.
|
46
|
-
#
|
47
|
-
# A standard attribute which differs from the property attribute delegates
|
48
|
-
# to the property attribute, e.g. +study_protocol+ delegates to +studyProtocol+
|
49
|
-
# rather than aliasing +setStudyProtocol+. Redefining these methods results
|
50
|
-
# in a call to the redefined method. This contrasts with a Ruby alias,
|
51
|
-
# where each attribute alias is bound to the respective property reader or
|
52
|
-
# writer.
|
53
|
-
def define_java_attribute(pd)
|
54
|
-
if transient?(pd) then
|
55
|
-
logger.debug { "Ignoring #{name.demodulize} transient property #{pd.name}." }
|
56
|
-
return
|
57
|
-
end
|
58
|
-
# the standard underscore lower-case attributes
|
59
|
-
attr = create_java_attribute(pd)
|
60
|
-
# delegate the standard attribute accessors to the property accessors
|
61
|
-
alias_attribute_property(attr, pd.name)
|
62
|
-
# add special wrappers
|
63
|
-
wrap_java_attribute(attr, pd)
|
64
|
-
# create Ruby alias for boolean, e.g. alias :empty? for :empty
|
65
|
-
if pd.property_type.name[/\w+$/].downcase == 'boolean' then
|
66
|
-
# strip leading is_, if any, before appending question mark
|
67
|
-
aliaz = attr.to_s[/^(is_)?(\w+)/, 2] << '?'
|
68
|
-
delegate_to_attribute(aliaz, attr)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Adds a filter to the attribute access method for the property descriptor pd if it is a String or Date.
|
73
|
-
def wrap_java_attribute(attribute, pd)
|
74
|
-
if pd.property_type == Java::JavaLang::String.java_class then
|
75
|
-
wrap_java_string_attribute(attribute, pd)
|
76
|
-
elsif pd.property_type == Java::JavaUtil::Date.java_class then
|
77
|
-
wrap_java_date_attribute(attribute, pd)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Adds a to_s filter to this Class's String property access methods.
|
82
|
-
def wrap_java_string_attribute(attribute, pd)
|
83
|
-
# filter the attribute writer
|
84
|
-
awtr = "#{attribute}=".to_sym
|
85
|
-
pwtr = pd.write_method.name.to_sym
|
86
|
-
define_method(awtr) do |value|
|
87
|
-
stdval = value.to_s unless value.nil_or_empty?
|
88
|
-
send(pwtr, stdval)
|
89
|
-
end
|
90
|
-
logger.debug { "Filtered #{qp} #{awtr} method with non-String -> String converter." }
|
91
|
-
end
|
92
|
-
|
93
|
-
# Adds a date parser filter to this Class's Date property access methods.
|
94
|
-
def wrap_java_date_attribute(attribute, pd)
|
95
|
-
# filter the attribute reader
|
96
|
-
prdr = pd.read_method.name.to_sym
|
97
|
-
define_method(attribute) do
|
98
|
-
value = send(prdr)
|
99
|
-
Java::JavaUtil::Date === value ? value.to_ruby_date : value
|
100
|
-
end
|
101
|
-
|
102
|
-
# filter the attribute writer
|
103
|
-
awtr = "#{attribute}=".to_sym
|
104
|
-
pwtr = pd.write_method.name.to_sym
|
105
|
-
define_method(awtr) do |value|
|
106
|
-
value = Java::JavaUtil::Date.from_ruby_date(value) if ::Date === value
|
107
|
-
send(pwtr, value)
|
108
|
-
end
|
109
|
-
|
110
|
-
logger.debug { "Filtered #{qp} #{attribute} and #{awtr} methods with Java Date <-> Ruby Date converter." }
|
111
|
-
end
|
112
|
-
|
113
|
-
# Aliases the methods _aliaz_ and _aliaz=_ to _property_ and _property=_, resp.,
|
114
|
-
# where _property_ is the Java property name for the attribute.
|
115
|
-
def alias_attribute_property(aliaz, attribute)
|
116
|
-
# strip the Java reader and writer is/get/set prefix and make a symbol
|
117
|
-
prdr, pwtr = attribute_metadata(attribute).property_accessors
|
118
|
-
alias_method(aliaz, prdr)
|
119
|
-
writer = "#{aliaz}=".to_sym
|
120
|
-
alias_method(writer, pwtr)
|
121
|
-
end
|
122
|
-
|
123
|
-
# Makes a standard attribute for the given property descriptor.
|
124
|
-
# Adds a camelized Java-like alias to the standard attribute.
|
125
|
-
#
|
126
|
-
# @quirk caTissue DE annotation collection attributes are often misnamed,
|
127
|
-
# e.g. +histologic_grade+ for a +HistologicGrade+ collection attribute.
|
128
|
-
# This is fixed by adding a pluralized alias, e.g. +histologic_grades+.
|
129
|
-
#
|
130
|
-
# @return a new attribute symbol created for the given PropertyDescriptor pd
|
131
|
-
def create_java_attribute(pd)
|
132
|
-
# make the attribute metadata
|
133
|
-
attr_md = JavaAttribute.new(pd, self)
|
134
|
-
add_attribute_metadata(attr_md)
|
135
|
-
# the property name is an alias for the standard attribute
|
136
|
-
std_attr = attr_md.to_sym
|
137
|
-
prop_attr = pd.name.to_sym
|
138
|
-
delegate_to_attribute(prop_attr, std_attr) unless prop_attr == std_attr
|
139
|
-
|
140
|
-
# alias a misnamed collection attribute, if necessary
|
141
|
-
if attr_md.collection? then
|
142
|
-
name = std_attr.to_s
|
143
|
-
if name.singularize == name then
|
144
|
-
aliaz = name.pluralize.to_sym
|
145
|
-
if aliaz != name then
|
146
|
-
logger.debug { "Adding annotation #{qp} alias #{aliaz} to the misnamed collection attribute #{std_attr}..." }
|
147
|
-
delegate_to_attribute(aliaz, std_attr)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
std_attr
|
153
|
-
end
|
154
|
-
|
155
|
-
# Defines methods _aliaz_ and _aliaz=_ which calls the standard _attribute_ and
|
156
|
-
# _attribute=_ accessor methods, resp.
|
157
|
-
# Calling rather than aliasing the attribute accessor allows the aliaz accessor to
|
158
|
-
# reflect a change to the attribute accessor.
|
159
|
-
def delegate_to_attribute(aliaz, attribute)
|
160
|
-
if aliaz == attribute then raise MetadataError.new("Cannot delegate #{self} #{aliaz} to itself.") end
|
161
|
-
rdr, wtr = attribute_metadata(attribute).accessors
|
162
|
-
define_method(aliaz) { send(rdr) }
|
163
|
-
define_method("#{aliaz}=".to_sym) { |value| send(wtr, value) }
|
164
|
-
add_alias(aliaz, attribute)
|
165
|
-
end
|
166
|
-
|
167
|
-
# Makes a new synthetic attribute for each _method_ => _original_ hash entry.
|
168
|
-
#
|
169
|
-
# @param (see Class#offset_attr_accessor)
|
170
|
-
def offset_attribute(hash, offset=nil)
|
171
|
-
offset_attr_accessor(hash, offset)
|
172
|
-
hash.each { |attr, original| add_attribute(attr, attribute_metadata(original).type) }
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|