caruby-core 1.4.2 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
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)