caruby-core 1.4.2 → 1.4.3

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.
Files changed (52) hide show
  1. data/History.txt +10 -0
  2. data/lib/caruby/cli/command.rb +10 -8
  3. data/lib/caruby/database/fetched_matcher.rb +28 -39
  4. data/lib/caruby/database/lazy_loader.rb +101 -0
  5. data/lib/caruby/database/persistable.rb +190 -167
  6. data/lib/caruby/database/persistence_service.rb +21 -7
  7. data/lib/caruby/database/persistifier.rb +185 -0
  8. data/lib/caruby/database/reader.rb +106 -176
  9. data/lib/caruby/database/saved_matcher.rb +56 -0
  10. data/lib/caruby/database/search_template_builder.rb +1 -1
  11. data/lib/caruby/database/sql_executor.rb +8 -7
  12. data/lib/caruby/database/store_template_builder.rb +134 -61
  13. data/lib/caruby/database/writer.rb +252 -52
  14. data/lib/caruby/database.rb +88 -67
  15. data/lib/caruby/domain/attribute_initializer.rb +16 -0
  16. data/lib/caruby/domain/attribute_metadata.rb +161 -72
  17. data/lib/caruby/domain/id_alias.rb +22 -0
  18. data/lib/caruby/domain/inversible.rb +91 -0
  19. data/lib/caruby/domain/merge.rb +116 -35
  20. data/lib/caruby/domain/properties.rb +1 -1
  21. data/lib/caruby/domain/reference_visitor.rb +207 -71
  22. data/lib/caruby/domain/resource_attributes.rb +93 -80
  23. data/lib/caruby/domain/resource_dependency.rb +22 -97
  24. data/lib/caruby/domain/resource_introspection.rb +21 -28
  25. data/lib/caruby/domain/resource_inverse.rb +134 -0
  26. data/lib/caruby/domain/resource_metadata.rb +41 -19
  27. data/lib/caruby/domain/resource_module.rb +42 -33
  28. data/lib/caruby/import/java.rb +8 -9
  29. data/lib/caruby/migration/migrator.rb +20 -7
  30. data/lib/caruby/migration/resource_module.rb +0 -2
  31. data/lib/caruby/resource.rb +132 -351
  32. data/lib/caruby/util/cache.rb +4 -1
  33. data/lib/caruby/util/class.rb +48 -1
  34. data/lib/caruby/util/collection.rb +54 -18
  35. data/lib/caruby/util/inflector.rb +7 -0
  36. data/lib/caruby/util/options.rb +35 -31
  37. data/lib/caruby/util/partial_order.rb +1 -1
  38. data/lib/caruby/util/properties.rb +2 -2
  39. data/lib/caruby/util/stopwatch.rb +16 -8
  40. data/lib/caruby/util/transitive_closure.rb +1 -1
  41. data/lib/caruby/util/visitor.rb +342 -328
  42. data/lib/caruby/version.rb +1 -1
  43. data/lib/caruby/yard/resource_metadata_handler.rb +8 -0
  44. data/lib/caruby.rb +2 -0
  45. metadata +10 -9
  46. data/lib/caruby/database/saved_merger.rb +0 -131
  47. data/lib/caruby/domain/annotatable.rb +0 -25
  48. data/lib/caruby/domain/annotation.rb +0 -23
  49. data/lib/caruby/import/annotatable_class.rb +0 -28
  50. data/lib/caruby/import/annotation_class.rb +0 -27
  51. data/lib/caruby/import/annotation_module.rb +0 -67
  52. data/lib/caruby/migration/resource.rb +0 -8
@@ -4,7 +4,7 @@ require 'caruby/domain/java_attribute_metadata'
4
4
  module CaRuby
5
5
  # ResourceMetadata mix-in to infer attribute meta-data from Java properties.
6
6
  module ResourceIntrospection
7
-
7
+
8
8
  private
9
9
 
10
10
  # Defines the Java property attribute and standard attribute methods, e.g.
@@ -106,6 +106,13 @@ module CaRuby
106
106
  alias_method(writer, pwtr)
107
107
  end
108
108
 
109
+ # Makes a standard attribute for the given property descriptor.
110
+ # Adds a camelized Java-like alias to the standard attribute.
111
+ #
112
+ # caTissue alert - DE annotation collection attributes are often misnamed,
113
+ # e.g. +histologic_grade+ for a +HistologicGrade+ collection attribute.
114
+ # This is fixed by adding a pluralized alias, e.g. +histologic_grades+.
115
+ #
109
116
  # @return a new attribute symbol created for the given PropertyDescriptor pd
110
117
  def create_java_attribute(pd)
111
118
  # make the attribute metadata
@@ -115,6 +122,19 @@ module CaRuby
115
122
  std_attr = attr_md.to_sym
116
123
  prop_attr = pd.name.to_sym
117
124
  delegate_to_attribute(prop_attr, std_attr) unless prop_attr == std_attr
125
+
126
+ # alias a misnamed collection attribute, if necessary
127
+ if attr_md.collection? then
128
+ name = std_attr.to_s
129
+ if name.singularize == name then
130
+ aliaz = name.pluralize.to_sym
131
+ if aliaz != name then
132
+ logger.debug { "Adding annotation #{qp} alias #{aliaz} to the misnamed collection attribute #{std_attr}..." }
133
+ delegate_to_attribute(aliaz, std_attr)
134
+ end
135
+ end
136
+ end
137
+
118
138
  std_attr
119
139
  end
120
140
 
@@ -136,32 +156,5 @@ module CaRuby
136
156
  offset_attr_accessor(hash, offset)
137
157
  hash.each { |attr, original| add_attribute(attr, attribute_metadata(original).type) }
138
158
  end
139
-
140
- # Modifies the given attribute writer method if necessary to update the given inverse_attr value.
141
- # This method is called on dependent and attributes qualified as inversible.
142
- #
143
- # @see ResourceDependency#add_owner
144
- # @see ResourceAttributes#set_attribute_inverse
145
- def add_inverse_updater(attribute, inverse)
146
- attr_md = attribute_metadata(attribute)
147
- # the reader and writer methods
148
- reader, writer = attr_md.accessors
149
- logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{writer}..." }
150
- # the inverse atttribute metadata
151
- inv_attr_md = attr_md.inverse_attribute_metadata
152
- # the inverse attribute reader and writer
153
- inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
154
- # redefine the writer method to update the inverse
155
- # by delegating to the Resource instance set_inversible_attribute
156
- redefine_method(writer) do |old_wtr|
157
- # the attribute reader and (superseded) writer
158
- accessors = [reader, old_wtr]
159
- if inv_attr_md.collection? then
160
- lambda { |owner| add_to_inverse_collection(owner, accessors, inv_rdr) }
161
- else
162
- lambda { |owner| set_inversible_noncollection_attribute(owner, accessors, inv_wtr) }
163
- end
164
- end
165
- end
166
159
  end
167
160
  end
@@ -0,0 +1,134 @@
1
+ require 'caruby/import/java'
2
+ require 'caruby/domain/java_attribute_metadata'
3
+
4
+ module CaRuby
5
+ # ResourceMetadata mix-in to infer and set inverse attributes.
6
+ module ResourceInverse
7
+
8
+ protected
9
+
10
+ # Sets the given bi-directional association attribute's inverse.
11
+ #
12
+ # @param [Symbol] attribute the subject attribute
13
+ # @param [Symbol] the attribute inverse
14
+ # @raise [TypeError] if the inverse type is incompatible with this Resource
15
+ def set_attribute_inverse(attribute, inverse)
16
+ attr_md = attribute_metadata(attribute)
17
+ # return if inverse is already set
18
+ return if attr_md.inverse == inverse
19
+ # the default inverse
20
+ inverse ||= attr_md.type.detect_inverse_attribute(self)
21
+ # the inverse attribute meta-data
22
+ inv_md = attr_md.type.attribute_metadata(inverse)
23
+ # if the attribute is the many side of a 1:M relation, then delegate to the one side.
24
+ if attr_md.collection? and not inv_md.collection? then
25
+ return attr_md.type.set_attribute_inverse(inverse, attribute)
26
+ end
27
+ # this class must be the same as or a subclass of the inverse attribute type
28
+ unless self <= inv_md.type then
29
+ raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
30
+ end
31
+
32
+ # if the attribute is defined by this class, then set the inverse in the attribute metadata.
33
+ # otherwise, make a new attribute metadata specialized for this class.
34
+ unless attr_md.declarer == self then
35
+ attr_md = attr_md.dup
36
+ attr_md.declarer = self
37
+ add_attribute_metadata(attribute, inverse)
38
+ end
39
+ attr_md.inverse = inverse
40
+
41
+ # If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
42
+ unless attr_md.collection? then
43
+ add_inverse_updater(attribute, inverse)
44
+ unless attr_md.type == inv_md.type or inv_md.collection? then
45
+ attr_md.type.delegate_writer_to_inverse(inverse, attribute)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Detects an unambiguous attribute which refers to the given referencing class.
51
+ # If there is exactly one attribute with the given return type, then that attribute is chosen.
52
+ # Otherwise, the attribute whose name matches the underscored referencing class name is chosen,
53
+ # if any.
54
+ #
55
+ # @param [Class] klass the referencing class
56
+ # @return [Symbol, nil] the inverse attribute for the given referencing class and inverse,
57
+ # or nil if no owner attribute was detected
58
+ def detect_inverse_attribute(klass)
59
+ # the candidate attributes which return the referencing type
60
+ candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type }.to_a
61
+ attr = detect_inverse_attribute_from_candidates(klass, candidates)
62
+ if attr then
63
+ logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
64
+ else
65
+ logger.debug { "#{qp} #{klass.qp} inverse attribute was not detected." }
66
+ end
67
+ attr
68
+ end
69
+
70
+ # Redefines the attribute writer method to delegate to its inverse writer.
71
+ # This is done to enforce inverse integrity.
72
+ #
73
+ # For a +Person+ attribute +account+ with inverse +holder+, this is equivalent to the following:
74
+ # class Person
75
+ # alias :set_account :account=
76
+ # def account=(acct)
77
+ # acct.holder = self if acct
78
+ # set_account(acct)
79
+ # end
80
+ # end
81
+ def delegate_writer_to_inverse(attribute, inverse)
82
+ attr_md = attribute_metadata(attribute)
83
+ # nothing to do if no inverse
84
+ inv_attr_md = attr_md.inverse_attribute_metadata || return
85
+ logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type.qp}.#{inv_attr_md}..." }
86
+ # redefine the write to set the dependent inverse
87
+ redefine_method(attr_md.writer) do |old_writer|
88
+ # delegate to the CaRuby::Resource set_inverse method
89
+ lambda { |dep| set_inverse(dep, old_writer, inv_attr_md.writer) }
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # @param klass (see #detect_inverse_attribute)
96
+ # @param [<Symbol>] candidates the attributes constrained to the target type
97
+ # @return (see #detect_inverse_attribute)
98
+ def detect_inverse_attribute_from_candidates(klass, candidates)
99
+ return if candidates.empty?
100
+ # there can be at most one owner attribute per owner.
101
+ return candidates.first.to_sym if candidates.size == 1
102
+ # by convention, if more than one attribute references the owner type,
103
+ # then the attribute named after the owner type is the owner attribute
104
+ tgt = klass.name[/\w+$/].underscore.to_sym
105
+ tgt if candidates.detect { |attr| attr == tgt }
106
+ end
107
+
108
+ # Modifies the given attribute writer method if necessary to update the given inverse_attr value.
109
+ # This method is called on dependent and attributes qualified as inversible.
110
+ #
111
+ # @see #set_attribute_inverse
112
+ def add_inverse_updater(attribute, inverse)
113
+ attr_md = attribute_metadata(attribute)
114
+ # the reader and writer methods
115
+ rdr, wtr = attr_md.accessors
116
+ logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{wtr}..." }
117
+ # the inverse atttribute metadata
118
+ inv_attr_md = attr_md.inverse_attribute_metadata
119
+ # the inverse attribute reader and writer
120
+ inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
121
+
122
+ # Redefine the writer method to update the inverse by delegating to the inverse
123
+ redefine_method(wtr) do |old_wtr|
124
+ # the attribute reader and (superseded) writer
125
+ accessors = [rdr, old_wtr]
126
+ if inv_attr_md.collection? then
127
+ lambda { |other| add_to_inverse_collection(other, accessors, inv_rdr) }
128
+ else
129
+ lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -1,9 +1,10 @@
1
1
  require 'caruby/util/collection'
2
2
  require 'caruby/import/java'
3
3
  require 'caruby/domain/java_attribute_metadata'
4
- require 'caruby/domain/resource_attributes'
5
4
  require 'caruby/domain/resource_introspection'
5
+ require 'caruby/domain/resource_inverse'
6
6
  require 'caruby/domain/resource_dependency'
7
+ require 'caruby/domain/resource_attributes'
7
8
 
8
9
  module CaRuby
9
10
  # Exception raised if a meta-data setting is missing or invalid.
@@ -11,7 +12,7 @@ module CaRuby
11
12
 
12
13
  # Adds introspected metadata to a Class.
13
14
  module ResourceMetadata
14
- include ResourceIntrospection, ResourceDependency, ResourceAttributes
15
+ include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes
15
16
 
16
17
  attr_reader :domain_module
17
18
 
@@ -21,7 +22,6 @@ module CaRuby
21
22
  # by the {#domain_module} method.
22
23
  def add_metadata(mod)
23
24
  @domain_module = mod
24
- init_attributes # in ResourceAttributes
25
25
  introspect # in ResourceIntrospection
26
26
  end
27
27
 
@@ -31,8 +31,43 @@ module CaRuby
31
31
  attr_md.type if attr_md.domain?
32
32
  end
33
33
 
34
+ # Returns an empty value for the given attribute.
35
+ # * If this class is not abstract, then the empty value is the initialized value.
36
+ # * Otherwise, if the attribute is a Java primitive number then zero.
37
+ # * Otherwise, if the attribute is a Java primitive boolean then +false+.
38
+ # * Otherwise, the empty value is nil.
39
+ #
40
+ # @param [Symbol] attribute the target attribute
41
+ # @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
42
+ def empty_value(attribute)
43
+ if abstract? then
44
+ attr_md = attribute_metadata(attribute)
45
+ # the Java property type
46
+ jtype = attr_md.property_descriptor.property_type if JavaAttributeMetadata === attr_md
47
+ # A primitive is either a boolean or a number (String is not primitive).
48
+ if jtype and jtype.primitive? then
49
+ type.name == 'boolean' ? false : 0
50
+ end
51
+ else
52
+ # Since this class is not abstract, create a prototype instance on demand and make
53
+ # a copy of the initialized collection value from that instance.
54
+ @prototype ||= new
55
+ value = @prototype.send(attribute) || return
56
+ value.class.new
57
+ end
58
+ end
59
+
34
60
  # Prints this classifier's content to the log.
35
61
  def pretty_print(q)
62
+
63
+
64
+ # KLUDGE - if not inited then bail
65
+ if @attr_md_hash.nil? then
66
+ q.text(qp)
67
+ return
68
+ end
69
+
70
+
36
71
  # the Java property descriptors
37
72
  property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
38
73
  # build a map of relevant display label => attributes
@@ -71,16 +106,6 @@ module CaRuby
71
106
  end
72
107
 
73
108
  protected
74
-
75
- # Adds the given subclass to this class's {ResourceMetadata#domain_module}.
76
- def introspect_subclass(klass)
77
- # introspect this class if necessary
78
- unless @domain_module then
79
- raise TypeError.new("Can't introspect #{qp}") unless superclass and superclass.include?(Resource)
80
- superclass.introspect_subclass(self)
81
- end
82
- @domain_module.add_class(klass)
83
- end
84
109
 
85
110
  def self.extend_class(klass, mod)
86
111
  klass.extend(self)
@@ -105,12 +130,9 @@ module CaRuby
105
130
 
106
131
  def format_print_value(value)
107
132
  case value
108
- when String then
109
- value
110
- when Class then
111
- value.qp
112
- else
113
- value.pp_s(:single_line)
133
+ when String then value
134
+ when Class then value.qp
135
+ else value.pp_s(:single_line)
114
136
  end
115
137
  end
116
138
  end
@@ -47,21 +47,23 @@ module CaRuby
47
47
  module ResourceModule
48
48
  # Adds the given klass to this ResourceModule. The class is extended with ResourceMetadata methods.
49
49
  def add_class(klass)
50
- @resource_module__loaded ||= Set.new
50
+ @rsc_classes ||= Set.new
51
51
  # add superclass if necessary
52
- unless @resource_module__loaded.include?(klass.superclass) or klass.superclass == Java::JavaLang::Object then
52
+ unless @rsc_classes.include?(klass.superclass) or klass.superclass == Java::JavaLang::Object then
53
53
  # the domain module includes the superclass on demand
54
54
  const_get(klass.superclass.name[/\w+$/].to_sym)
55
55
  end
56
56
  ResourceMetadata.extend_class(klass, self)
57
- @resource_module__loaded << klass
57
+ @rsc_classes << klass
58
58
  end
59
59
 
60
- # Reinclude the mixin in all loaded classes.
61
- def mixin_changed
62
- @resource_module__loaded.each { |klass| klass.instance_eval { include CaRuby::Resource } }
60
+ # @return [Module] the resource mix-in module (default {Resouce})
61
+ def mixin
62
+ @mixin || Resource
63
63
  end
64
64
 
65
+ # @return [{Symbol => Object}] the caBIG application access properties
66
+ # @see #load_access_properties
65
67
  def access_properties
66
68
  @resource_module__props ||= load_access_properties
67
69
  end
@@ -70,9 +72,9 @@ module CaRuby
70
72
  # The default file path is a period followed by the lower-case application name in the home directory,
71
73
  # e.g. +~/.clincaltrials+.
72
74
  #
73
- # The property file format is a series of property definitions in the form _property_ +=+ _value_.
75
+ # The property file format is a series of property definitions in the form _property_: _value_.
74
76
  # The supported properties include the following:
75
- # * +path+ - the application client Java jar file directories
77
+ # * +path+ - the application client Java directories
76
78
  # * +user+ - the application service login
77
79
  # * +password+ - the application service password
78
80
  # * +database+ - the application database name
@@ -82,8 +84,13 @@ module CaRuby
82
84
  # * +database_type+ - the application database type, + mysql+ or +oracle+ (default +mysql+)
83
85
  # * +database_driver+ - the application database connection driver (default is the database type default)
84
86
  # * +database_port+ - the application database connection port
85
- # # The +path+ value is one or more directories separated by a semi-colon(;) or colon (:)
86
- # # The jar files in the +path+ directory are added to the caRuby execution Java classpath.
87
+ #
88
+ # The +path+ value is one or more directories separated by a semi-colon(;) or colon (:)
89
+ # Each path directory and all jar files within the directory are added to the caRuby execution
90
+ # Java classpath.
91
+ #
92
+ # @param [String, nil] file the property file, or nil for the default location
93
+ # @return [{Symbol => Object}] the loaded caBIG application access properties
87
94
  def load_access_properties(file=nil)
88
95
  # If a file was specified, then it must exist.
89
96
  if file and not File.exists?(file) then
@@ -146,7 +153,7 @@ module CaRuby
146
153
  sym = base_name.camelize.to_sym
147
154
  sym_file_hash[sym] = file
148
155
  autoload(sym, file)
149
- end
156
+ end
150
157
 
151
158
  # load the domain class definitions
152
159
  sym_file_hash.each do |sym, file|
@@ -163,10 +170,29 @@ module CaRuby
163
170
  end
164
171
 
165
172
  # Extends the mod module with Java class support. See the class documentation for details.
173
+ #
174
+ # @param [Symbol] symbol the missing constant
166
175
  def const_missing(symbol)
167
176
  autoload?(symbol) ? super : import_domain_class(symbol)
168
177
  end
169
178
 
179
+ # Returns the domain class for class_name, or nil if none in this module.
180
+ def domain_type_with_name(class_name)
181
+ pkg, base = split_class_name(class_name)
182
+ return unless pkg.nil? or pkg == @java_package
183
+ begin
184
+ type = const_get(base)
185
+ rescue JavaIncludeError
186
+ # no such domain type; not an error.
187
+ # other exceptions indicate that there was a domain type but could not be loaded; these exceptions propagate up the call stack
188
+ logger.debug("#{base} is not a #{qp} Java class.")
189
+ return
190
+ end
191
+ type if type < Resource
192
+ end
193
+
194
+ private
195
+
170
196
  # Imports the domain Java class with specified class name_or_sym.
171
197
  # This method enables the domain class extensions for storing and retrieving domain objects.
172
198
  # The class is added to this module.
@@ -218,10 +244,10 @@ module CaRuby
218
244
  # the Resource import stack
219
245
  @import_stack ||= []
220
246
  @import_stack.push klass
221
- # include the Resource mixin into the imported class
222
- mixin = @mixin
223
- klass.instance_eval { include mixin }
224
- logger.info("Imported #{qp} class #{klass.qp}")
247
+ # include the Resource mixin in the imported class
248
+ inc = "include #{mixin}"
249
+ klass.instance_eval(inc)
250
+
225
251
  # if we are back to top of the stack, then print the imported Resources
226
252
  if klass == @import_stack.first then
227
253
  # a referenced class could be imported on demand in the course of printing a referencing class;
@@ -235,23 +261,6 @@ module CaRuby
235
261
  klass
236
262
  end
237
263
 
238
- # Returns the domain class for class_name, or nil if none in this module.
239
- def domain_type_with_name(class_name)
240
- pkg, base = split_class_name(class_name)
241
- return unless pkg.nil? or pkg == @java_package
242
- begin
243
- type = const_get(base)
244
- rescue JavaIncludeError
245
- # no such domain type; not an error.
246
- # other exceptions indicate that there was a domain type but could not be loaded; these exceptions propagate up the call stack
247
- logger.debug("#{base} is not a #{qp} Java class.")
248
- return
249
- end
250
- type if type < Resource
251
- end
252
-
253
- private
254
-
255
264
  # The property/value matcher, e.g.:
256
265
  # host: jacardi
257
266
  # host = jacardi
@@ -273,7 +282,7 @@ module CaRuby
273
282
  end
274
283
  end
275
284
 
276
- # Returns the [package prefix, base class symbol] pair.
285
+ # @return [(String, Symbol)] the [package prefix, base class symbol] pair
277
286
  def split_class_name(class_name)
278
287
  # the package prefix, including the period
279
288
  package = Java.java_package_name(class_name)