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.
Files changed (126) hide show
  1. data/Gemfile +9 -0
  2. data/History.md +5 -1
  3. data/lib/caruby.rb +3 -5
  4. data/lib/caruby/caruby-src.tar.gz +0 -0
  5. data/lib/caruby/database.rb +53 -69
  6. data/lib/caruby/database/application_service.rb +25 -0
  7. data/lib/caruby/database/cache.rb +60 -0
  8. data/lib/caruby/database/fetched_matcher.rb +52 -38
  9. data/lib/caruby/database/lazy_loader.rb +4 -4
  10. data/lib/caruby/database/operation.rb +34 -0
  11. data/lib/caruby/database/persistable.rb +171 -86
  12. data/lib/caruby/database/persistence_service.rb +32 -34
  13. data/lib/caruby/database/persistifier.rb +100 -43
  14. data/lib/caruby/database/reader.rb +107 -85
  15. data/lib/caruby/database/reader_template_builder.rb +60 -0
  16. data/lib/caruby/database/saved_matcher.rb +3 -3
  17. data/lib/caruby/database/sql_executor.rb +88 -17
  18. data/lib/caruby/database/writer.rb +213 -177
  19. data/lib/caruby/database/writer_template_builder.rb +334 -0
  20. data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
  21. data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
  22. data/lib/caruby/{util → helpers}/person.rb +3 -3
  23. data/lib/caruby/{util → helpers}/properties.rb +7 -9
  24. data/lib/caruby/{util → helpers}/roman.rb +2 -2
  25. data/lib/caruby/{util → helpers}/version.rb +1 -1
  26. data/lib/caruby/json/deserializer.rb +2 -2
  27. data/lib/caruby/json/serializer.rb +49 -7
  28. data/lib/caruby/metadata.rb +30 -0
  29. data/lib/caruby/metadata/java_property.rb +21 -0
  30. data/lib/caruby/metadata/propertied.rb +191 -0
  31. data/lib/caruby/metadata/property.rb +22 -0
  32. data/lib/caruby/metadata/property_characteristics.rb +201 -0
  33. data/lib/caruby/migration/migratable.rb +11 -182
  34. data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
  35. data/lib/caruby/resource.rb +20 -823
  36. data/lib/caruby/version.rb +1 -1
  37. data/test/lib/caruby/database/cache_test.rb +54 -0
  38. data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
  39. data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
  40. data/test/lib/caruby/helpers/properties_test.rb +34 -0
  41. data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
  42. data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
  43. data/test/lib/helper.rb +7 -0
  44. metadata +161 -214
  45. data/lib/caruby/cli/application.rb +0 -36
  46. data/lib/caruby/cli/command.rb +0 -202
  47. data/lib/caruby/csv/csv_mapper.rb +0 -159
  48. data/lib/caruby/csv/csvio.rb +0 -203
  49. data/lib/caruby/database/search_template_builder.rb +0 -56
  50. data/lib/caruby/database/store_template_builder.rb +0 -278
  51. data/lib/caruby/domain.rb +0 -193
  52. data/lib/caruby/domain/attribute.rb +0 -584
  53. data/lib/caruby/domain/attributes.rb +0 -628
  54. data/lib/caruby/domain/dependency.rb +0 -225
  55. data/lib/caruby/domain/id_alias.rb +0 -22
  56. data/lib/caruby/domain/importer.rb +0 -183
  57. data/lib/caruby/domain/introspection.rb +0 -176
  58. data/lib/caruby/domain/inverse.rb +0 -172
  59. data/lib/caruby/domain/inversible.rb +0 -90
  60. data/lib/caruby/domain/java_attribute.rb +0 -173
  61. data/lib/caruby/domain/merge.rb +0 -185
  62. data/lib/caruby/domain/metadata.rb +0 -142
  63. data/lib/caruby/domain/mixin.rb +0 -35
  64. data/lib/caruby/domain/properties.rb +0 -95
  65. data/lib/caruby/domain/reference_visitor.rb +0 -428
  66. data/lib/caruby/domain/uniquify.rb +0 -50
  67. data/lib/caruby/import/java.rb +0 -387
  68. data/lib/caruby/migration/migrator.rb +0 -918
  69. data/lib/caruby/migration/resource_module.rb +0 -9
  70. data/lib/caruby/migration/uniquify.rb +0 -17
  71. data/lib/caruby/util/attribute_path.rb +0 -44
  72. data/lib/caruby/util/cache.rb +0 -56
  73. data/lib/caruby/util/class.rb +0 -149
  74. data/lib/caruby/util/collection.rb +0 -1152
  75. data/lib/caruby/util/domain_extent.rb +0 -46
  76. data/lib/caruby/util/file_separator.rb +0 -65
  77. data/lib/caruby/util/inflector.rb +0 -27
  78. data/lib/caruby/util/log.rb +0 -95
  79. data/lib/caruby/util/math.rb +0 -12
  80. data/lib/caruby/util/merge.rb +0 -59
  81. data/lib/caruby/util/module.rb +0 -18
  82. data/lib/caruby/util/options.rb +0 -97
  83. data/lib/caruby/util/partial_order.rb +0 -35
  84. data/lib/caruby/util/pretty_print.rb +0 -204
  85. data/lib/caruby/util/stopwatch.rb +0 -74
  86. data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
  87. data/lib/caruby/util/transitive_closure.rb +0 -55
  88. data/lib/caruby/util/tree.rb +0 -48
  89. data/lib/caruby/util/trie.rb +0 -37
  90. data/lib/caruby/util/uniquifier.rb +0 -30
  91. data/lib/caruby/util/validation.rb +0 -20
  92. data/lib/caruby/util/visitor.rb +0 -365
  93. data/lib/caruby/util/weak_hash.rb +0 -36
  94. data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
  95. data/test/lib/caruby/csv/csvio_test.rb +0 -69
  96. data/test/lib/caruby/database/persistable_test.rb +0 -92
  97. data/test/lib/caruby/domain/domain_test.rb +0 -112
  98. data/test/lib/caruby/domain/inversible_test.rb +0 -99
  99. data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
  100. data/test/lib/caruby/import/java_test.rb +0 -80
  101. data/test/lib/caruby/import/mixed_case_test.rb +0 -14
  102. data/test/lib/caruby/migration/test_case.rb +0 -102
  103. data/test/lib/caruby/test_case.rb +0 -230
  104. data/test/lib/caruby/util/cache_test.rb +0 -23
  105. data/test/lib/caruby/util/class_test.rb +0 -61
  106. data/test/lib/caruby/util/collection_test.rb +0 -398
  107. data/test/lib/caruby/util/command_test.rb +0 -55
  108. data/test/lib/caruby/util/domain_extent_test.rb +0 -60
  109. data/test/lib/caruby/util/file_separator_test.rb +0 -30
  110. data/test/lib/caruby/util/inflector_test.rb +0 -12
  111. data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
  112. data/test/lib/caruby/util/merge_test.rb +0 -83
  113. data/test/lib/caruby/util/module_test.rb +0 -25
  114. data/test/lib/caruby/util/options_test.rb +0 -59
  115. data/test/lib/caruby/util/partial_order_test.rb +0 -42
  116. data/test/lib/caruby/util/pretty_print_test.rb +0 -85
  117. data/test/lib/caruby/util/properties_test.rb +0 -50
  118. data/test/lib/caruby/util/stopwatch_test.rb +0 -18
  119. data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
  120. data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
  121. data/test/lib/caruby/util/tree_test.rb +0 -23
  122. data/test/lib/caruby/util/trie_test.rb +0 -14
  123. data/test/lib/caruby/util/visitor_test.rb +0 -278
  124. data/test/lib/caruby/util/weak_hash_test.rb +0 -45
  125. data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
  126. data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,18 +1,10 @@
1
- require 'caruby/util/version'
2
- require 'caruby/database'
3
- require 'caruby/util/stopwatch'
1
+ require 'jinx/helpers/stopwatch'
2
+ require 'caruby/helpers/version'
3
+ require 'caruby/database/application_service'
4
4
 
5
5
  module CaRuby
6
- # HQLCriteria is required for the query_hql method.
7
- java_import Java::gov.nih.nci.common.util.HQLCriteria
8
-
9
- # The encapsulated caBIG service class.
10
- java_import Java::gov.nih.nci.system.applicationservice.ApplicationServiceProvider
11
-
12
- # This import is not strictly necessary, but works around Ticket #5.
13
- java_import Java::gov.nih.nci.system.comm.client.ApplicationServiceClientImpl
14
-
15
- # A PersistenceService wraps a caCORE application service.
6
+ # A PersistenceService is a database mediator which implements the {#query} {#create}, {#update}
7
+ # and {#delete} methods.
16
8
  class PersistenceService
17
9
  # The service name.
18
10
  attr_reader :name
@@ -27,13 +19,14 @@ module CaRuby
27
19
  # @option opts [String] :host the service host (default +localhost+)
28
20
  # @option opts [String] :version the caTissue version identifier
29
21
  def initialize(name, opts={})
22
+ CaRuby::PersistenceService.import_java_classes
30
23
  @name = name
31
24
  ver_opt = opts[:version]
32
25
  @version = ver_opt.to_s.to_version if ver_opt
33
26
  @host = opts[:host] || default_host
34
27
  @port = opts[:port] || 8080
35
28
  @url = "http://#{@host}:#{@port}/#{@name}/http/remoteService"
36
- @timer = Stopwatch.new
29
+ @timer = Jinx::Stopwatch.new
37
30
  logger.debug { "Created persistence service #{name} at #{@host}:#{@port}." }
38
31
  end
39
32
 
@@ -63,7 +56,7 @@ module CaRuby
63
56
  dispatch { |svc| svc.create_object(obj) }
64
57
  rescue Exception => e
65
58
  logger.error("Error creating #{obj} - #{e.message}\n#{dump(obj)}")
66
- raise
59
+ raise e
67
60
  end
68
61
  end
69
62
 
@@ -73,8 +66,8 @@ module CaRuby
73
66
  begin
74
67
  dispatch { |svc| svc.update_object(obj) }
75
68
  rescue Exception => e
76
- logger.error("Error updating #{obj} - #{e.message}\n#{dump(obj)}")
77
- raise
69
+ logger.error("Error updating #{obj} - #{e.message}\n#{dump(obj)}")
70
+ raise e
78
71
  end
79
72
  end
80
73
 
@@ -84,21 +77,16 @@ module CaRuby
84
77
  begin
85
78
  dispatch { |svc| svc.remove_object(obj) }
86
79
  rescue Exception => e
87
- logger.error("Error deleting #{obj} - #{e.message}\n#{dump(obj)}")
88
- raise
80
+ logger.error("Error deleting #{obj} - #{e.message}\n#{dump(obj)}")
81
+ raise e
89
82
  end
90
83
  end
91
84
 
92
- # Returns a freshly initialized ApplicationServiceProvider remote instance.
85
+ # Returns the {ApplicationService} remote instance.
93
86
  #
94
- # @quirk caCORE When more than one application service is used, each call to the service
95
- # must reinitialize the remote instance. E.g. this is done in the caTissue DE examples,
96
- # and is a good general practice.
97
- #
98
- # @return [ApplicationServiceProvider] the CaCORE service provider wrapped by this PersistenceService
87
+ # @return the CaCORE service provider wrapped by this PersistenceService
99
88
  def app_service
100
- logger.debug { "Connecting to service provider at #{@url}..." }
101
- ApplicationServiceProvider.remote_instance(@url)
89
+ ApplicationService.for(@url)
102
90
  end
103
91
 
104
92
  private
@@ -144,13 +132,13 @@ module CaRuby
144
132
  logger.debug { "Building HQLCriteria..." }
145
133
  criteria = HQLCriteria.new(hql)
146
134
  target = hql[/from\s+(\S+)/i, 1]
147
- raise DatabaseError.new("HQL does not contain a FROM clause: #{hql}") unless target
135
+ Jinx.fail(DatabaseError, "HQL does not contain a FROM clause: #{hql}") unless target
148
136
  logger.debug { "Submitting search on target class #{target} with the following HQL:\n #{hql}" }
149
137
  begin
150
138
  dispatch { |svc| svc.query(criteria, target) }
151
139
  rescue Exception => e
152
140
  logger.error("Error querying on HQL - #{$!}:\n#{hql}")
153
- raise
141
+ raise e
154
142
  end
155
143
  end
156
144
 
@@ -161,9 +149,9 @@ module CaRuby
161
149
  logger.debug { "Searching using template #{template.qp}#{', path ' + path.join('.') unless path.empty?}..." }
162
150
  # collect the class search path from the reference attribute domain type Java class names
163
151
  class_name_path = []
164
- path.inject(template.class) do |type, attr|
165
- ref_type = type.domain_type(attr)
166
- raise DatabaseError.new("Attribute in search attribute path #{path.join('.')} is not a #{type} domain reference attribute: #{attr}") if ref_type.nil?
152
+ path.inject(template.class) do |type, pa|
153
+ ref_type = type.domain_type(pa)
154
+ Jinx.fail(DatabaseError, "Property in search attribute path #{path.join('.')} is not a #{type} domain reference attribute: #{pa}") if ref_type.nil?
167
155
  class_name_path << ref_type.java_class.name
168
156
  ref_type
169
157
  end
@@ -175,7 +163,7 @@ module CaRuby
175
163
  dispatch { |svc| svc.search(reverse_class_name_path.join(','), template) }
176
164
  rescue Exception => e
177
165
  logger.error("Error searching on template #{template}#{', path ' + path.join('.') unless path.empty?} - #{$!}\n#{dump(template)}")
178
- raise
166
+ raise e
179
167
  end
180
168
  end
181
169
 
@@ -198,7 +186,17 @@ module CaRuby
198
186
  end
199
187
 
200
188
  def dump(obj)
201
- Resource === obj ? obj.dump : obj.to_s
189
+ Jinx::Resource === obj ? obj.dump : obj.to_s
202
190
  end
191
+
192
+ private
193
+
194
+ # Imports this class's Java classes on demand.
195
+ def self.import_java_classes
196
+ return if const_defined?(:HQLCriteria)
197
+ # HQLCriteria is required for the query_hql method.
198
+ java_import Java::gov.nih.nci.common.util.HQLCriteria
199
+ end
200
+
203
201
  end
204
202
  end
@@ -1,3 +1,4 @@
1
+ require 'jinx/resource/reference_visitor'
1
2
  require 'caruby/database/persistable'
2
3
  require 'caruby/database/lazy_loader'
3
4
 
@@ -6,42 +7,53 @@ module CaRuby
6
7
  # @return [LazyLoader] this database's lazy loader
7
8
  attr_reader :lazy_loader
8
9
 
9
- # Database Persistable mediator.
10
+ # Database {Persistable} mediator.
11
+ # A module which includes {Persistifier} must implement the {Reader#fetch_association} method.
10
12
  module Persistifier
11
13
  # Adds query capability to this Database.
12
14
  def initialize
13
15
  super
14
- @ftchd_vstr = ReferenceVisitor.new { |ref| ref.class.fetched_domain_attributes }
16
+ @ftchd_vstr = Jinx::ReferenceVisitor.new { |ref| ref.class.fetched_domain_attributes }
15
17
  # the demand loader
16
- @lazy_loader = LazyLoader.new { |obj, attr| lazy_load(obj, attr) }
18
+ @lazy_loader = LazyLoader.new { |obj, pa| lazy_load(obj, pa) }
19
+ end
20
+
21
+ # Clears the cache.
22
+ def clear
23
+ @cache.clear if @cache
17
24
  end
18
25
 
19
26
  private
20
27
 
21
28
  # Adds this database's lazy loader to the given domain object.
22
29
  #
23
- # @param [Resource] obj the domain object to lazy-load
30
+ # @param [Jinx::Resource] obj the domain object to lazy-load
24
31
  def add_lazy_loader(obj, attributes=nil)
25
32
  obj.add_lazy_loader(@lazy_loader, attributes)
26
33
  end
27
34
 
28
- # Loads the content of the given attribute.
29
- # The fetched references are persistified with {#persistify}.
35
+ # Loads the content of the given attribute. If the attribute is independent,
36
+ # then the fetched objects are replaced by corresponding cached objects,
37
+ # if cached. The fetched references are persistified with {#persistify}.
30
38
  #
31
- # @param [Resource] obj the domain object whose content is to be loaded
39
+ # @param [Jinx::Resource] obj the domain object whose content is to be loaded
32
40
  # @param [Symbol] attribute the attribute to load
33
- # @return [Resource, <Resource>, nil] the loaded value
41
+ # @return [Jinx::Resource, <Jinx::Resource>, nil] the loaded value
34
42
  def lazy_load(obj, attribute)
35
- fetched = fetch_association(obj, attribute)
36
- reconcile_fetched(fetched) if fetched
43
+ fetched = fetch_association(obj, attribute) || return
44
+ if obj.class.property(attribute).dependent? then
45
+ persistify(fetched)
46
+ else
47
+ reconcile_fetched(fetched)
48
+ end
37
49
  end
38
50
 
39
51
  # For each fetched domain object, if there is a corresponding cached object,
40
52
  # then the reconciled value is that cached object. Otherwise, the reconciled
41
53
  # object is the persistified fetched object.
42
54
  #
43
- # @param [Resource, <Resource>] fetched the fetched domain object(s)
44
- # @return [Resource, <Resource>] the reconciled domain object(s)
55
+ # @param [Jinx::Resource, <Jinx::Resource>] fetched the fetched domain object(s)
56
+ # @return [Jinx::Resource, <Jinx::Resource>] the reconciled domain object(s)
45
57
  def reconcile_fetched(fetched)
46
58
  if Enumerable === fetched then
47
59
  fetched.map { |ref| reconcile_fetched(ref) }
@@ -50,18 +62,17 @@ module CaRuby
50
62
  end
51
63
  end
52
64
 
53
- # @param [Resource] fetched the fetched domain object
54
- # @return [Resource] the corresponding cached object, if cached,
55
- # otherwise the fetched object
65
+ # @param [Jinx::Resource] fetched the fetched domain object
66
+ # @return [Jinx::Resource, nil] the corresponding cached object, if any
56
67
  def reconcile_cached(fetched)
57
- cached = @cache[fetched]
68
+ cached = @cache[fetched] if @cache
58
69
  if cached then
59
70
  logger.debug { "Replaced fetched #{fetched} with cached #{cached}." }
60
71
  end
61
72
  cached
62
73
  end
63
74
 
64
- # This method copies each result domain object into a new object of the same type.
75
+ # This method clears the given toxic domain objects fetched from the database.
65
76
  # The copy nondomain attribute values are set to the fetched object values.
66
77
  # The copy fetched reference attribute values are set to a copy of the result references.
67
78
  #
@@ -75,9 +86,9 @@ module CaRuby
75
86
  # set to the parent. Rather, it is a toxic caCORE reference which must be purged. This
76
87
  # leaves an empty reference which must be lazy-loaded, which is inefficient and inconsistent.
77
88
  # This situation is rectified in this detoxify method by setting the dependent owner
78
- # attribute to the fetched owner in the detoxification {ReferenceVisitor} copy-match-merge.
89
+ # attribute to the fetched owner in the detoxification {Jinx::ReferenceVisitor} copy-match-merge.
79
90
  #
80
- # @return [Resource, <Resource>] the detoxified object(s)
91
+ # @return [Jinx::Resource, <Jinx::Resource>] the detoxified object(s)
81
92
  def detoxify(toxic)
82
93
  return if toxic.nil?
83
94
  if toxic.collection? then
@@ -85,6 +96,7 @@ module CaRuby
85
96
  else
86
97
  logger.debug { "Detoxifying the toxic caCORE result #{toxic.qp}..." }
87
98
  @ftchd_vstr.visit(toxic) { |ref| clear_toxic_attributes(ref) }
99
+ logger.debug { "Detoxified the toxic caCORE result #{toxic.qp}." }
88
100
  end
89
101
  toxic
90
102
  end
@@ -92,59 +104,81 @@ module CaRuby
92
104
  # Sets each of the toxic attributes in the given domain object to the corresponding
93
105
  # {Metadata#empty_value}.
94
106
  #
95
- # @param [Resource] toxic the toxic domain object
107
+ # @param [Jinx::Resource] toxic the toxic domain object
96
108
  def clear_toxic_attributes(toxic)
97
- attrs = toxic.class.toxic_attributes
98
- return if attrs.empty?
99
- logger.debug { "Clearing toxic #{toxic.qp} attributes #{attrs.to_series}..." }
100
- attrs.each_pair do |attr, attr_md|
109
+ # The result class might not have been previously referenced. In that case,
110
+ # introspect the class.
111
+ ensure_introspected(toxic.class)
112
+ pas = toxic.class.toxic_attributes
113
+ return if pas.empty?
114
+ logger.debug { "Clearing toxic #{toxic.qp} attributes #{pas.to_series}..." }
115
+ pas.each_pair do |pa, prop|
101
116
  # skip non-Java attributes
102
- next unless attr_md.java_property?
117
+ next unless prop.java_property?
103
118
  # the empty or nil value to set
104
- value = toxic.class.empty_value(attr)
119
+ value = toxic.class.empty_value(pa)
105
120
  # Use the Java writer method rather than the standard attribute writer method.
106
121
  # The standard attribute writer enforces inverse integrity, which potential requires
107
122
  # accessing the current toxic value. The Java writer bypasses inverse integrity.
108
- reader, writer = attr_md.property_accessors
123
+ reader, writer = prop.property_accessors
109
124
  # clear the attribute
110
125
  toxic.send(writer, value)
111
126
  end
112
127
  end
113
128
 
114
- # Persistifies the given domain object and all of its dependents. Sets the inverses
115
- # using #{#set_inverses} to enforce inverse integrity.
129
+ # Persistifies the given domain object and all of its dependents as follows:
130
+ # * Ensure that the object class is introspected.
131
+ # * Set the inverses to enforce inverse integrity.
116
132
  #
117
133
  # @param (see #persistify_object)
118
134
  # @raise [ArgumentError] if obj is a collection and other is not nil
119
135
  def persistify(obj, other=nil)
120
136
  if obj.collection? then
121
- if other then raise ArgumentError.new("Database reader persistify other argument not supported") end
137
+ if other then Jinx.fail(ArgumentError, "Database reader persistify other argument not supported") end
122
138
  obj.each { |ref| persistify(ref) }
123
139
  return obj
124
140
  end
141
+ # The attribute type is introspected, but the object might be an unintrospected
142
+ # subtype. Introspect the object class if necessary.
143
+ ensure_introspected(obj.class)
125
144
  # set the inverses before recursing to dependents
126
145
  set_inverses(obj)
127
146
  # recurse to dependents before adding a lazy loader to the owner
128
- obj.each_dependent { |dep| persistify(dep) if dep.identifier }
147
+ obj.dependents.each { |dep| persistify(dep) if dep.identifier }
129
148
  persistify_object(obj, other)
130
149
  end
131
150
 
151
+ # Introspects the given class, if necessary. The class must be either introspected
152
+ # or a subclass of an introspected class.
153
+ #
154
+ # @param [Class] klass the class to introspect if necessary
155
+ def ensure_introspected(klass)
156
+ unless klass.introspected? then
157
+ sc = klass.superclass
158
+ ensure_introspected(sc)
159
+ logger.debug { "Introspecting the fetched object class #{klass}..." }
160
+ # Resolving the class name in the context of the domain module
161
+ # introspects the class.
162
+ sc.domain_module.const_get(klass.name.demodulize)
163
+ end
164
+ end
165
+
132
166
  # Takes a {Persistable#snapshot} of obj to track changes, adds a lazy loader and
133
167
  # adds the object to the cache.
134
168
  #
135
169
  # If the other fetched source object is given, then the obj snapshot is updated
136
170
  # with the non-nil values from other.
137
171
  #
138
- # @param [Resource] obj the domain object to make persistable
139
- # @param [Resource] other the source domain object
140
- # @return [Resource] obj
172
+ # @param [Jinx::Resource] obj the domain object to make persistable
173
+ # @param [Jinx::Resource] other the source domain object
174
+ # @return [Jinx::Resource] obj
141
175
  def persistify_object(obj, other=nil)
142
176
  # take a snapshot of the database content
143
177
  snapshot(obj, other)
144
178
  # add lazy loader to the unfetched attributes
145
179
  add_lazy_loader(obj)
146
180
  # add to the cache
147
- @cache.add(obj)
181
+ encache(obj)
148
182
  obj
149
183
  end
150
184
 
@@ -155,12 +189,12 @@ module CaRuby
155
189
  #
156
190
  # @param obj (see #persistify_object)
157
191
  def set_inverses(obj)
158
- obj.class.domain_attributes.each_pair do |attr, attr_md|
159
- inv_md = attr_md.inverse_metadata || next
160
- if inv_md.collection? then
161
- obj.send(attr).enumerate { |ref| ref.send(inv_md.to_sym) << obj }
192
+ obj.class.domain_attributes.each_pair do |pa, prop|
193
+ inv_prop = prop.inverse_property || next
194
+ if inv_prop.collection? then
195
+ obj.send(pa).enumerate { |ref| ref.send(inv_prop.attribute) << obj }
162
196
  else
163
- obj.send(attr).enumerate { |ref| ref.set_attribute(inv_md.to_sym, obj) }
197
+ obj.send(pa).enumerate { |ref| ref.set_property_value(inv_prop.attribute, obj) }
164
198
  end
165
199
  end
166
200
  end
@@ -170,9 +204,9 @@ module CaRuby
170
204
  # values into the obj snapshot, replacing an existing obj non-domain value with the
171
205
  # corresponding other attribute value if and only if the other attribute value is non-nil.
172
206
  #
173
- # @param [Resource] obj the domain object to snapshot
174
- # @param [Resource] the source domain object
175
- # @return [Resource] the obj snapshot, updated with source content if necessary
207
+ # @param [Jinx::Resource] obj the domain object to snapshot
208
+ # @param [Jinx::Resource] the source domain object
209
+ # @return [Jinx::Resource] the obj snapshot, updated with source content if necessary
176
210
  def snapshot(obj, other=nil)
177
211
  # take a fresh snapshot
178
212
  obj.take_snapshot
@@ -180,6 +214,29 @@ module CaRuby
180
214
  # merge the other object content if available
181
215
  obj.merge_into_snapshot(other) if other
182
216
  end
217
+
218
+ # @param [Jinx::Resource] obj the object to cache
219
+ # @raise [ArgumentError] if the given item does not have an identifier
220
+ def encache(obj)
221
+ @cache ||= create_cache
222
+ @cache.add(obj)
223
+ end
224
+
225
+ # @quirk JRuby identifier is not a stable object when fetched from the database, i.e.:
226
+ # obj.identifier.equal?(obj.identifier) #=> false
227
+ # This is probably an artifact of jRuby Numeric -> Java Long conversion interaction
228
+ # combined with hash access use of the eql? method. Work-around is to convert the
229
+ # identifier to a Ruby Integer.
230
+ #
231
+ # @return [Cache] a new object cache
232
+ def create_cache
233
+ Cache.new do |obj|
234
+ if obj.identifier.nil? then
235
+ Jinx.fail(ArgumentError, "Can't cache object without identifier: #{obj}")
236
+ end
237
+ obj.identifier.to_s.to_i
238
+ end
239
+ end
183
240
  end
184
241
  end
185
242
  end
@@ -1,9 +1,9 @@
1
- require 'caruby/util/collection'
2
- require 'caruby/util/cache'
3
- require 'caruby/util/pretty_print'
4
- require 'caruby/domain/reference_visitor'
1
+ require 'jinx/helpers/collection'
2
+ require 'jinx/helpers/pretty_print'
3
+ require 'jinx/resource/merge_visitor'
4
+ require 'caruby/database/cache'
5
5
  require 'caruby/database/fetched_matcher'
6
- require 'caruby/database/search_template_builder'
6
+ require 'caruby/database/reader_template_builder'
7
7
 
8
8
  module CaRuby
9
9
  class Database
@@ -13,7 +13,7 @@ module CaRuby
13
13
  def initialize
14
14
  super
15
15
  # the query template builder
16
- @srch_tmpl_bldr = SearchTemplateBuilder.new
16
+ @srch_tmpl_bldr = TemplateBuilder.new
17
17
  # the fetch result matcher
18
18
  @matcher = FetchedMatcher.new
19
19
  # the fetched copier
@@ -23,7 +23,7 @@ module CaRuby
23
23
  copy
24
24
  end
25
25
  # visitor that merges the fetched object graph
26
- @ftchd_mrg_vstr = MergeVisitor.new(:matcher => @matcher, :copier => copier) { |ref| ref.class.fetched_domain_attributes }
26
+ @ftchd_mrg_vstr = Jinx::MergeVisitor.new(:matcher => @matcher, :copier => copier) { |ref| ref.class.fetched_domain_attributes }
27
27
  end
28
28
 
29
29
  # Returns an array of objects matching the specified query template and attribute path.
@@ -32,7 +32,7 @@ module CaRuby
32
32
  # is a String, then the HQL statement String is executed.
33
33
  #
34
34
  # Otherwise, the query condition is determined by the values set in the template.
35
- # The non-nil {Domain::Attributes#searchable_attributes} are used in the query.
35
+ # The non-nil {Propertied#searchable_attributes} are used in the query.
36
36
  #
37
37
  # The optional path arguments are attribute symbols from the template to the
38
38
  # destination class, e.g.:
@@ -48,9 +48,9 @@ module CaRuby
48
48
  # By contrast, caCORE API search result property access, by design, fails with an
49
49
  # obscure exception when the property is not lazy-loaded in Hibernate.
50
50
  #
51
- # @param [Resource, String] obj_or_hql the domain object or HQL to query
52
- # @param [<Attribute>] path the attribute path to search
53
- # @return [<Resource>] the domain objects which match the query
51
+ # @param [Jinx::Resource, String] obj_or_hql the domain object or HQL to query
52
+ # @param [<Property>] path the attribute path to search
53
+ # @return [<Jinx::Resource>] the domain objects which match the query
54
54
  def query(obj_or_hql, *path)
55
55
  # the detoxified caCORE query result
56
56
  result = query_safe(obj_or_hql, *path)
@@ -66,10 +66,10 @@ module CaRuby
66
66
  # If the :create option is set, then this method creates an object if the
67
67
  # find is unsuccessful.
68
68
  #
69
- # @param [Resource] obj the domain object to find
69
+ # @param [Jinx::Resource] obj the domain object to find
70
70
  # @param [Hash, Symbol] opts the find options
71
71
  # @option opts [Boolean] :create whether to create the object if it is not found
72
- # @return [Resource, nil] the domain object if found, nil otherwise
72
+ # @return [Jinx::Resource, nil] the domain object if found, nil otherwise
73
73
  # @raise [DatabaseError] if obj is not a domain object or more than object
74
74
  # matches the obj attribute values
75
75
  def find(obj, opts=nil)
@@ -88,7 +88,7 @@ module CaRuby
88
88
  # Returns whether the given domain object has a database identifier or exists in the database.
89
89
  # This method fetches the object from the database if necessary.
90
90
  #
91
- # @param [Resource, <Resource>] obj the domain object(s) to find
91
+ # @param [Jinx::Resource, <Jinx::Resource>] obj the domain object(s) to find
92
92
  # @return [Boolean] whether the domain object(s) exist in the database
93
93
  def exists?(obj)
94
94
  if obj.nil? then
@@ -131,7 +131,7 @@ module CaRuby
131
131
  path_s = path.join('.') unless path.empty?
132
132
  # guard against recursive call back into the same operation
133
133
  if query_redundant?(obj_or_hql, path_s) then
134
- raise DatabaseError.new("Query #{obj_or_hql.qp} #{path_s} recursively called in context #{print_operations}")
134
+ Jinx.fail(DatabaseError, "Query #{obj_or_hql.qp} #{path_s} recursively called in context #{print_operations}")
135
135
  end
136
136
  # perform the query
137
137
  perform(:query, obj_or_hql, :attribute => path_s) { query_with_path(obj_or_hql, path) }
@@ -142,7 +142,7 @@ module CaRuby
142
142
  end
143
143
 
144
144
  def query_subject_redundant?(s1, s2)
145
- s1 == s2 or (Resource === s1 and Resource === s2 and s1.identifier and s1.identifier == s2.identifier)
145
+ s1 == s2 or (Jinx::Resource === s1 and Jinx::Resource === s2 and s1.identifier and s1.identifier == s2.identifier)
146
146
  end
147
147
 
148
148
  # @return an array of objects matching the given query template and path
@@ -154,7 +154,7 @@ module CaRuby
154
154
  # gather the results of querying on those penultimate result objects with the last
155
155
  # attribute as the path
156
156
  unless path.empty? then
157
- if attribute.nil? then raise DatabaseError.new("Query path includes empty attribute: #{path.join('.')}.nil") end
157
+ if attribute.nil? then Jinx.fail(DatabaseError, "Query path includes empty attribute: #{path.join('.')}.nil") end
158
158
  logger.debug { "Decomposing query on #{obj_or_hql} with path #{path.join('.')}.#{attribute} into query on #{path.join('.')} followed by #{attribute}..." }
159
159
  return query_safe(obj_or_hql, *path).map { |parent| query_toxic(parent, attribute) }.flatten
160
160
  end
@@ -183,8 +183,15 @@ module CaRuby
183
183
 
184
184
  # Merges fetched into target. The fetched references are recursively merged.
185
185
  #
186
- # @param [Resource] source the fetched domain object result
187
- # @param [Resource] target the domain object find argument
186
+ # @quirk caCORE caCORE does not enforce reference integrity, i.e. if object _a_ has
187
+ # a reference path _a_.+b+ = _b_ and _b_.+a+ = _a_, then a search on _a_ with path
188
+ # +:b+ results in the reference path _a_ => _b_ => _a'_, where
189
+ # _a.identifier_ == _a'.identifier_ but _a_ != _a_'.
190
+ # This method remedies the caCORE defect by matching source references on a previously
191
+ # matched identifier where possible.
192
+ #
193
+ # @param [Jinx::Resource] source the fetched domain object result
194
+ # @param [Jinx::Resource] target the domain object find argument
188
195
  def merge_fetched(source, target)
189
196
  @ftchd_mrg_vstr.visit(source, target) { |src, tgt| tgt.copy_volatile_attributes(src) }
190
197
  end
@@ -197,7 +204,7 @@ module CaRuby
197
204
 
198
205
  def query_hql(hql)
199
206
  java_name = hql[/from\s+(\S+)/i, 1]
200
- raise DatabaseError.new("Could not determine target type from HQL: #{hql}") if java_name.nil?
207
+ Jinx.fail(DatabaseError, "Could not determine target type from HQL: #{hql}") if java_name.nil?
201
208
  tgt = Class.to_ruby(java_name)
202
209
  persistence_service(tgt).query(hql)
203
210
  end
@@ -205,16 +212,16 @@ module CaRuby
205
212
  # Returns an array of objects fetched from the database which matches
206
213
  # a template and follows the given optional domain attribute, if present.
207
214
  #
208
- # The search template is built by {SearchTemplateBuilder#build_template}.
215
+ # The search template is built by {TemplateBuilder#build_template}.
209
216
  # If a template could not be built and obj is dependent, then this method
210
217
  # queries the obj owner with a dependent filter.
211
218
  #
212
219
  # @quirk caCORE Bug #79 - API search with only id returns entire table.
213
220
  # Work around this bug by issuing a HQL query instead.
214
221
  #
215
- # @param [Resource] obj the query template object
222
+ # @param [Jinx::Resource] obj the query template object
216
223
  # @param [Symbol, nil] attribute the optional attribute to fetch
217
- # @return [<Resource>] the query result
224
+ # @return [<Jinx::Resource>] the query result
218
225
  def query_object(obj, attribute=nil)
219
226
  if obj.identifier then
220
227
  query_on_identifier(obj, attribute)
@@ -248,7 +255,7 @@ module CaRuby
248
255
 
249
256
  # the join attribute property
250
257
  if attribute then
251
- pd = obj.class.attribute_metadata(attribute).property_descriptor
258
+ pd = obj.class.property(attribute).property_descriptor
252
259
  hql.insert(0, "select #{sa}.#{pd.name} ")
253
260
  end
254
261
  logger.debug { "Querying on #{obj} #{attribute} using HQL identifier criterion..." }
@@ -265,10 +272,10 @@ module CaRuby
265
272
  # @return [Boolean] whether the query can be inverted
266
273
  def invertible_query?(obj, attribute=nil)
267
274
  return false if attribute.nil?
268
- attr_md = obj.class.attribute_metadata(attribute)
269
- return false if attr_md.type.abstract?
270
- inv_md = attr_md.inverse_metadata
271
- inv_md and inv_md.searchable? and finder_parameters(obj)
275
+ pa = obj.class.property(attribute)
276
+ return false if pa.type.abstract?
277
+ inv_prop = pa.inverse_property
278
+ inv_prop and inv_prop.searchable? and finder_parameters(obj)
272
279
  end
273
280
 
274
281
  # Queries the given query object attribute by querying an attribute type template which references obj.
@@ -279,18 +286,18 @@ module CaRuby
279
286
  #
280
287
  # @param (see #query_object)
281
288
  def query_with_inverted_reference(obj, attribute=nil)
282
- attr_md = obj.class.attribute_metadata(attribute)
283
- logger.debug { "Querying on #{obj.qp} #{attribute} by inverting the query as a #{attr_md.type.qp} #{attr_md.inverse} reference query..." }
289
+ pa = obj.class.property(attribute)
290
+ logger.debug { "Querying on #{obj.qp} #{attribute} by inverting the query as a #{pa.type.qp} #{pa.inverse} reference query..." }
284
291
  # the search reference template
285
292
  ref = finder_template(obj)
286
293
  # the attribute inverse query template
287
- tmpl = attr_md.type.new
294
+ tmpl = pa.type.new
288
295
  # the inverse attribute
289
- inv_md = tmpl.class.attribute_metadata(attr_md.inverse)
296
+ inv_prop = tmpl.class.property(pa.inverse)
290
297
  # The Java property writer to set the tmpl inverse to ref.
291
298
  # Use the property writer rather than the attribute writer in order to curtail automatically
292
- # adding tmpl to the ref attribute value when the inv_md attribute is set to ref.
293
- wtr = inv_md.property_writer
299
+ # adding tmpl to the ref attribute value when the inv_prop attribute is set to ref.
300
+ wtr = inv_prop.property_writer
294
301
  # parameterize tmpl with inverse ref
295
302
  tmpl.send(wtr, ref)
296
303
  # submit the query
@@ -312,7 +319,7 @@ module CaRuby
312
319
  # so it is done manually here.
313
320
  #
314
321
  # @param obj (see #find)
315
- # @return [Resource, nil] obj if there is a matching database record, nil otherwise
322
+ # @return [Jinx::Resource, nil] obj if there is a matching database record, nil otherwise
316
323
  # @raise [DatabaseError] if more than object matches the obj attribute values or if
317
324
  # the search object is a dependent entity that does not reference an owner
318
325
  def find_object(obj)
@@ -339,7 +346,12 @@ module CaRuby
339
346
  #
340
347
  # @see #find_object
341
348
  def fetch_object(obj)
342
- # make the finder template with key attributes
349
+ # If there is an identifier, then work around the caCORE identifier query bug by delegating
350
+ # to the HQL identifier query.
351
+ if obj.identifier then
352
+ return query_on_identifier(obj).first
353
+ end
354
+ # Make the finder template with key attributes.
343
355
  tmpl = finder_template(obj)
344
356
  # If a template could be made, then fetch on the template.
345
357
  # Otherwise, if there is an owner, then match on the fetched owner dependents.
@@ -361,45 +373,43 @@ module CaRuby
361
373
  msg = "More than one match for #{obj.class.qp} find with template #{template}."
362
374
  # it is an error to have an ambiguous result
363
375
  logger.error("Fetch error - #{msg}:\n#{obj}")
364
- raise DatabaseError.new(msg)
376
+ Jinx.fail(DatabaseError, msg)
365
377
  end
366
378
 
367
379
  result.first
368
380
  end
369
381
 
370
- # If obj is a dependent, then returns the obj owner dependent which matches obj.
371
- # Otherwise, returns nil.
382
+ # If the given domain object is a dependent with an unfetched owner, then this method fetches
383
+ # the owner and attempts to match the owner dependent to this object.
372
384
  #
373
- # @param [Resource] the domain object to fetch
374
- # @return [Resource, nil] the domain object if it matches a dependent, nil otherwise
385
+ # @param [Jinx::Resource] obj the domain object to fetch
386
+ # @return [Jinx::Resource, nil] the domain object if it matches a dependent, nil otherwise
375
387
  def fetch_object_by_fetching_owner(obj)
376
388
  owner = nil
377
- oattr = obj.class.owner_attributes.detect { |attr| owner = obj.send(attr) }
378
- return unless owner
389
+ oattr = obj.class.owner_attributes.detect { |pa| owner = obj.send(pa) }
390
+ return if owner.nil? or owner.fetched?
379
391
 
380
- logger.debug { "Querying #{obj.qp} by matching on the owner #{owner.qp} #{oattr} dependents..." }
381
- inv_md = obj.class.attribute_metadata(oattr)
382
- if inv_md.nil? then
383
- raise DatabaseError.new("#{dep.class.qp} owner attribute #{oattr} does not have a #{owner.class.qp} inverse dependent attribute.")
392
+ logger.debug { "Querying #{obj.qp} by matching on the #{oattr} owner #{owner.qp} dependents..." }
393
+ inv_prop = obj.class.property(oattr)
394
+ if inv_prop.nil? then
395
+ Jinx.fail(DatabaseError, "#{dep.class.qp} owner attribute #{oattr} does not have a #{owner.class.qp} inverse dependent attribute.")
384
396
  end
385
- inverse = inv_md.inverse
397
+ inv = inv_prop.inverse
386
398
  # fetch the owner if necessary
387
- unless owner.identifier then
388
- find(owner) || return
389
- # if obj dependent was fetched with owner, then done
390
- if obj.identifier then
391
- logger.debug { "Found #{obj.qp} by fetching the owner #{owner}." }
392
- return obj
393
- end
399
+ find(owner) || return
400
+ # if obj dependent was fetched with owner, then done
401
+ if obj.identifier then
402
+ logger.debug { "Found #{obj.qp} by fetching the owner #{owner}." }
403
+ return obj
394
404
  end
395
405
 
396
406
  # try to match a fetched owner dependent
397
- deps = lazy_loader.enable { owner.send(inverse) }
407
+ deps = lazy_loader.enable { owner.send(inv) }
398
408
  if obj.identifier then
399
- logger.debug { "Found #{obj.qp} by fetching the owner #{owner} #{inverse} dependent #{deps.qp}." }
409
+ logger.debug { "Found #{obj.qp} by fetching the owner #{owner} #{inv} dependents." }
400
410
  return obj
401
411
  else
402
- logger.debug { "#{obj.qp} does not match a fetched owner #{owner} #{inverse} dependent #{deps.qp}." }
412
+ logger.debug { "#{obj.qp} does not match one of the fetched owner #{owner} #{inv} dependents #{deps}." }
403
413
  nil
404
414
  end
405
415
  end
@@ -415,7 +425,8 @@ module CaRuby
415
425
  @srch_tmpl_bldr.build_template(obj, hash)
416
426
  end
417
427
 
418
- # Fetches the given obj attribute from the database.
428
+ # Fetches the given object attribute value from the database.
429
+ #
419
430
  # @quirk caCORE there is no association fetch for caCORE 3.1 and earlier;
420
431
  # caCORE 4 association search is not yet adequately proven in caRuby testing.
421
432
  # Fall back on a general query instead (the devil we know). See also the
@@ -432,35 +443,46 @@ module CaRuby
432
443
  # the detoxified copy loses reference integrity. E.g. a query on the children attribute of
433
444
  # a parent object forces lazy load of each child => parent reference separately resolving
434
445
  # in separate parent copies. There is no recognition that the children reference the parent
435
- # which generated the query. This anomaly is partially rectified in this fetch_association
436
- # method by setting the fetched objects inverse to the given search target object. The
437
- # inconsistent and inefficient caCORE behavior is further corrected by setting inverse
438
- # owners when the fetch result is persistified, as described in {Persistifier#persistify}.
439
- # Callers who do not persistify the result should call {Persistifier#set_inverses} on the
440
- # result.
441
- #
442
- # @param [Resource] obj the search target object
446
+ # which generated the query. This anomaly cannot be rectified in this fetch_association
447
+ # method by setting the fetched objects inverse to the given search target object, since
448
+ # this method does not modify the search target. This hazardous and inefficient caCORE
449
+ # behavior is partially rectified by setting the fetched object inverse references to
450
+ # a copy of the search target. If the search target has been fetched, then it is cached.
451
+ # Therefore, calling {Persistifier#persistify} on the fetched objects will reconcile
452
+ # the referenced search target copy with the search target. This reconciliation is done
453
+ # by the caller, e.g. the lazy loader.
454
+ #
455
+ # @param [Jinx::Resource] obj the search target object
443
456
  # @param [Symbol] attribute the association to fetch
457
+ # @return [Jinx::Resource, <Jinx::Resource>, nil] the attribute value
444
458
  # @raise [DatabaseError] if the search target object does not have an identifier
445
459
  def fetch_association(obj, attribute)
446
460
  logger.debug { "Fetching association #{attribute} for #{obj}..." }
447
461
  # load the object if necessary
448
462
  unless exists?(obj) then
449
- raise DatabaseError.new("Can't fetch an association since the referencing object is not found in the database: #{obj}")
463
+ Jinx.fail(DatabaseError, "Can't fetch an association since the referencing object is not found in the database: #{obj}")
450
464
  end
451
465
  # fetch the reference
452
466
  result = query_safe(obj, attribute)
453
- # set the result inverse references
454
- inv_md = obj.class.attribute_metadata(attribute).inverse_metadata
455
- if inv_md and not inv_md.collection? then
456
- inv_obj = obj.copy(:identifier)
457
- result.each do |ref|
458
- logger.debug { "Setting fetched #{obj} #{attribute} value #{ref} inverse #{inv_md} to #{obj} copy #{inv_obj.qp}..." }
459
- ref.send(inv_md.writer, inv_obj)
460
- end
467
+ # Set the inverse to a place-holder copy of the search target.
468
+ # If this method is called by a lazy loader, then the caller will
469
+ # subsequently reconcile the reference with the cached search target.
470
+ prop = obj.class.property(attribute)
471
+ ip = prop.inverse_property
472
+ if ip and not ip.collection? then
473
+ place_holder = obj.copy(:identifier)
474
+ result.each { |ref| ref.send(ip.writer, place_holder) }
461
475
  end
462
- # unbracket the result if the attribute is not a collection
463
- obj.class.attribute_metadata(attribute).collection? ? result : result.first
476
+ # Unbracket the result if the search propery is not a collection.
477
+ prop.collection? ? result : result.first
478
+ end
479
+
480
+ # Fetches the given object attribute reference from the database and sets the property value.
481
+ #
482
+ # @param (see #fetch_association)
483
+ # @return (see #fetch_association)
484
+ def load_association(obj, attribute)
485
+ obj.set_property_value(attribute, fetch_association(obj, attribute))
464
486
  end
465
487
 
466
488
  # @return [{Symbol => Object}, nil] the find operation key attributes, or nil if there is no complete key
@@ -481,7 +503,7 @@ module CaRuby
481
503
  # the key must be non-trivial
482
504
  return if attributes.nil_or_empty?
483
505
  # the attribute => value hash
484
- attributes.to_compact_hash { |attr| finder_parameter(obj, attr) or return }
506
+ attributes.to_compact_hash { |pa| finder_parameter(obj, pa) or return }
485
507
  end
486
508
 
487
509
  # @return a non-empty, existing find parameter for the given attribute
@@ -508,10 +530,10 @@ module CaRuby
508
530
  # in the database.
509
531
  #
510
532
  # Raises DatabaseError if the value is nil.
511
- def finder_attribute_value_exists?(obj, attr)
512
- value = obj.send(attr)
533
+ def finder_attribute_value_exists?(obj, pa)
534
+ value = obj.send(pa)
513
535
  return false if value.nil?
514
- obj.class.nondomain_attribute?(attr) or value.identifier
536
+ obj.class.nondomain_attribute?(pa) or value.identifier
515
537
  end
516
538
 
517
539
  # Sets the template attribute to a new search reference object created from source.
@@ -520,12 +542,12 @@ module CaRuby
520
542
  # @quirk caCORE The search template must break inverse integrity by clearing an owner inverse reference,
521
543
  # since a dependent => onwer => dependent cycle causes a caCORE search infinite loop.
522
544
  #
523
- # @return [Resource, nil] the search reference, or nil if source does not exist in the database
545
+ # @return [Jinx::Resource, nil] the search reference, or nil if source does not exist in the database
524
546
  def add_search_template_reference(template, source, attribute)
525
547
  return if not exists?(source)
526
548
  ref = source.copy(:identifier)
527
- template.set_attribute(attribute, ref)
528
- inverse = template.class.attribute_metadata(attribute).derived_inverse
549
+ template.set_property_value(attribute, ref)
550
+ inverse = template.class.property(attribute).derived_inverse
529
551
  ref.clear_attribute(inverse) if inverse
530
552
  logger.debug { "Search reference parameter #{attribute} for #{template.qp} set to #{ref} copied from #{source.qp}" }
531
553
  ref