caruby-core 1.4.7 → 1.4.9
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.
- data/History.txt +11 -0
- data/README.md +1 -1
- data/lib/caruby/cli/command.rb +27 -3
- data/lib/caruby/csv/csv_mapper.rb +2 -0
- data/lib/caruby/csv/csvio.rb +187 -169
- data/lib/caruby/database.rb +33 -16
- data/lib/caruby/database/lazy_loader.rb +23 -23
- data/lib/caruby/database/persistable.rb +32 -18
- data/lib/caruby/database/persistence_service.rb +20 -7
- data/lib/caruby/database/reader.rb +22 -21
- data/lib/caruby/database/search_template_builder.rb +7 -9
- data/lib/caruby/database/sql_executor.rb +52 -27
- data/lib/caruby/database/store_template_builder.rb +18 -13
- data/lib/caruby/database/writer.rb +107 -44
- data/lib/caruby/domain/attribute_metadata.rb +35 -25
- data/lib/caruby/domain/java_attribute_metadata.rb +43 -20
- data/lib/caruby/domain/merge.rb +9 -5
- data/lib/caruby/domain/reference_visitor.rb +4 -3
- data/lib/caruby/domain/resource_attributes.rb +52 -12
- data/lib/caruby/domain/resource_dependency.rb +129 -42
- data/lib/caruby/domain/resource_introspection.rb +1 -1
- data/lib/caruby/domain/resource_inverse.rb +20 -3
- data/lib/caruby/domain/resource_metadata.rb +20 -4
- data/lib/caruby/domain/resource_module.rb +190 -124
- data/lib/caruby/import/java.rb +39 -19
- data/lib/caruby/migration/migratable.rb +31 -6
- data/lib/caruby/migration/migrator.rb +126 -40
- data/lib/caruby/migration/uniquify.rb +0 -1
- data/lib/caruby/resource.rb +28 -5
- data/lib/caruby/util/attribute_path.rb +0 -2
- data/lib/caruby/util/class.rb +8 -5
- data/lib/caruby/util/collection.rb +5 -3
- data/lib/caruby/util/domain_extent.rb +0 -3
- data/lib/caruby/util/options.rb +10 -9
- data/lib/caruby/util/person.rb +41 -12
- data/lib/caruby/util/pretty_print.rb +1 -1
- data/lib/caruby/util/validation.rb +0 -28
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +26 -9
- data/test/lib/caruby/migration/test_case.rb +103 -0
- data/test/lib/caruby/test_case.rb +231 -0
- data/test/lib/caruby/util/class_test.rb +2 -2
- data/test/lib/caruby/util/visitor_test.rb +3 -2
- data/test/lib/examples/galena/clinical_trials/migration/participant_test.rb +28 -0
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +40 -0
- metadata +195 -170
- data/lib/caruby/domain/attribute_initializer.rb +0 -16
- data/test/lib/caruby/util/validation_test.rb +0 -14
data/lib/caruby/database.rb
CHANGED
@@ -6,16 +6,15 @@ require 'caruby/util/options'
|
|
6
6
|
require 'caruby/util/visitor'
|
7
7
|
require 'caruby/util/inflector'
|
8
8
|
require 'caruby/database/persistable'
|
9
|
-
require 'caruby/database/
|
9
|
+
require 'caruby/database/persistence_service'
|
10
10
|
require 'caruby/database/reader'
|
11
11
|
require 'caruby/database/writer'
|
12
|
-
require 'caruby/database/
|
13
|
-
|
14
|
-
# the caBIG client classes
|
15
|
-
import 'gov.nih.nci.system.applicationservice.ApplicationServiceProvider'
|
16
|
-
import 'gov.nih.nci.system.comm.client.ClientSession'
|
12
|
+
require 'caruby/database/persistifier'
|
17
13
|
|
18
14
|
module CaRuby
|
15
|
+
# The caBIG client session class.
|
16
|
+
java_import Java::gov.nih.nci.system.comm.client.ClientSession
|
17
|
+
|
19
18
|
# Database operation error.
|
20
19
|
class DatabaseError < RuntimeError; end
|
21
20
|
|
@@ -43,7 +42,7 @@ module CaRuby
|
|
43
42
|
# store method. CaRuby::Resource sets reasonable default values, recognizes application dependencies and steers
|
44
43
|
# around caBIG idiosyncracies to the extent possible.
|
45
44
|
class Database
|
46
|
-
include Reader, Writer, Persistifier
|
45
|
+
include Reader, Writer, Persistifier
|
47
46
|
|
48
47
|
# Database CRUD operation.
|
49
48
|
class Operation
|
@@ -78,6 +77,24 @@ module CaRuby
|
|
78
77
|
|
79
78
|
# Creates a new Database with the specified service name and options.
|
80
79
|
#
|
80
|
+
# caCORE alert - obtaining a caCORE session instance mysteriously depends on referencing the
|
81
|
+
# application service first. Therefore, the default persistence service appService method must
|
82
|
+
# be called after it is instantiated and before the session is instantiated. However, when
|
83
|
+
# the appService method is called just before a session is acquired, then this call corrupts
|
84
|
+
# the object state of existing objects.
|
85
|
+
#
|
86
|
+
# Specifically, when a CaTissue::CollectionProtocol is created which references a
|
87
|
+
# CaTissue::CollectionProtocolRegistration which in turn references a CaTissue::Participant,
|
88
|
+
# then the call to PersistenceService.appService replaces the CaTissue::Participant
|
89
|
+
# reference with a difference CaTissue::Participant instance. The work-around for
|
90
|
+
# this extremely bizarre bug is to call appService immediately after instantiating
|
91
|
+
# the default persistence service.
|
92
|
+
#
|
93
|
+
# This bug might be a low-level JRuby-Java-caCORE-Hibernate confusion where something in
|
94
|
+
# caCORE stomps on an existing JRuby object graph. To reproduce, move the appService call
|
95
|
+
# to the start_session method and run PCBIN::MigrationTest#test_save with all but the
|
96
|
+
# verify_save(:biopsy, BIOPSY_OPTS) line commented out.
|
97
|
+
#
|
81
98
|
# @param [String] service_name the name of the default {PersistenceService}
|
82
99
|
# @param [{Symbol => String}] opts access options
|
83
100
|
# @option opts [String] :host application service host name
|
@@ -96,6 +113,7 @@ module CaRuby
|
|
96
113
|
port = Options.get(:port, opts)
|
97
114
|
# class => service hash; default is the catissuecore app service
|
98
115
|
@def_persist_svc = PersistenceService.new(service_name, :host => host, :port => port)
|
116
|
+
@def_persist_svc.app_service
|
99
117
|
@persistence_services = [@def_persist_svc].to_set
|
100
118
|
@cls_svc_hash = Hash.new(@def_persist_svc)
|
101
119
|
# the create/update nested operations
|
@@ -149,11 +167,12 @@ module CaRuby
|
|
149
167
|
# Subclasses can override for specialized services. A session is
|
150
168
|
# started on demand if necessary.
|
151
169
|
#
|
152
|
-
# @param [Persistable] obj the domain object
|
170
|
+
# @param [Persistable, Class] obj the domain object or {Resource} class
|
153
171
|
# @return [PersistanceService] the service for the domain object
|
154
|
-
def persistence_service(
|
155
|
-
|
156
|
-
|
172
|
+
def persistence_service(klass)
|
173
|
+
unless Class === klass then raise ArgumentError.new("#{self} persistence_service argument is not a Class: {#klass.qp}") end
|
174
|
+
start_session if @session.nil?
|
175
|
+
@def_persist_svc
|
157
176
|
end
|
158
177
|
|
159
178
|
# Adds the given service to this database.
|
@@ -244,11 +263,9 @@ module CaRuby
|
|
244
263
|
|
245
264
|
# Initializes the default application service.
|
246
265
|
def start_session
|
247
|
-
raise DatabaseError.new('Application user option missing')
|
248
|
-
raise DatabaseError.new('Application password option missing')
|
249
|
-
|
250
|
-
@def_persist_svc.app_service
|
251
|
-
@session = ClientSession.instance()
|
266
|
+
if @user.nil? then raise DatabaseError.new('Application user option missing') end
|
267
|
+
if @password.nil? then raise DatabaseError.new('Application password option missing') end
|
268
|
+
@session = ClientSession.instance
|
252
269
|
connect(@user, @password)
|
253
270
|
end
|
254
271
|
|
@@ -3,19 +3,40 @@ require 'caruby/database/fetched_matcher'
|
|
3
3
|
module CaRuby
|
4
4
|
class Database
|
5
5
|
# A LazyLoader fetches an association from the database on demand.
|
6
|
-
class LazyLoader
|
6
|
+
class LazyLoader
|
7
7
|
# Creates a new LazyLoader which calls the loader block on the subject.
|
8
8
|
#
|
9
9
|
# @yield [subject, attribute] fetches the given subject attribute value from the database
|
10
10
|
# @yieldparam [Resource] subject the domain object whose attribute is to be loaded
|
11
11
|
# @yieldparam [Symbol] attribute the domain attribute to load
|
12
12
|
def initialize(&loader)
|
13
|
-
|
13
|
+
@loader = loader
|
14
14
|
# the fetch result matcher
|
15
15
|
@matcher = FetchedMatcher.new
|
16
16
|
@enabled = true
|
17
17
|
end
|
18
18
|
|
19
|
+
# @param [Resource] subject the domain object whose attribute is to be loaded
|
20
|
+
# @param [Symbol] the domain attribute to load
|
21
|
+
# @yield (see #initialize)
|
22
|
+
# @yieldparam (see #initialize)
|
23
|
+
# @return the attribute value loaded from the database
|
24
|
+
# @raise [RuntimeError] if this loader is disabled
|
25
|
+
def load(subject, attribute)
|
26
|
+
if disabled? then raise RuntimeError.new("#{subject.qp} lazy load called on disabled loader") end
|
27
|
+
logger.debug { "Lazy-loading #{subject.qp} #{attribute}..." }
|
28
|
+
# the current value
|
29
|
+
oldval = subject.send(attribute)
|
30
|
+
# load the fetched value
|
31
|
+
fetched = @loader.call(subject, attribute)
|
32
|
+
# nothing to merge if nothing fetched
|
33
|
+
return oldval if fetched.nil_or_empty?
|
34
|
+
# merge the fetched into the attribute
|
35
|
+
logger.debug { "Merging #{subject.qp} fetched #{attribute} value #{fetched.qp}#{' into ' + oldval.qp if oldval}..." }
|
36
|
+
matches = @matcher.match(fetched.to_enum, oldval.to_enum)
|
37
|
+
subject.merge_attribute(attribute, fetched, matches)
|
38
|
+
end
|
39
|
+
|
19
40
|
# Disables this lazy loader. If the loader is already disabled, then this method is a no-op.
|
20
41
|
# Otherwise, if a block is given, then the lazy loader is reenabled after the block is executed.
|
21
42
|
#
|
@@ -74,27 +95,6 @@ module CaRuby
|
|
74
95
|
# @return [Boolean] true if this loader was previously disabled, false otherwise
|
75
96
|
def set_enabled
|
76
97
|
disabled? and (@enabled = true)
|
77
|
-
end
|
78
|
-
|
79
|
-
# @param [Resource] subject the domain object whose attribute is to be loaded
|
80
|
-
# @param [Symbol] the domain attribute to load
|
81
|
-
# @yield (see #initialize)
|
82
|
-
# @yieldparam (see #initialize)
|
83
|
-
# @return the attribute value loaded from the database
|
84
|
-
# @raise [RuntimeError] if this loader is disabled
|
85
|
-
def load(subject, attribute)
|
86
|
-
if disabled? then raise RuntimeError.new("#{subject.qp} lazy load called on disabled loader") end
|
87
|
-
logger.debug { "Lazy-loading #{subject.qp} #{attribute}..." }
|
88
|
-
# the current value
|
89
|
-
oldval = subject.send(attribute)
|
90
|
-
# load the fetched value
|
91
|
-
fetched = yield(subject, attribute)
|
92
|
-
# nothing to merge if nothing fetched
|
93
|
-
return oldval if fetched.nil_or_empty?
|
94
|
-
# merge the fetched into the attribute
|
95
|
-
logger.debug { "Merging #{subject.qp} fetched #{attribute} value #{fetched.qp}#{' into ' + oldval.qp if oldval}..." }
|
96
|
-
matches = @matcher.match(fetched.to_enum, oldval.to_enum)
|
97
|
-
subject.merge_attribute(attribute, fetched, matches)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
end
|
@@ -8,10 +8,8 @@ module CaRuby
|
|
8
8
|
# The Persistable mixin adds persistance capability. Every instance which includes Persistable
|
9
9
|
# must respond to an overrided {#database} method.
|
10
10
|
module Persistable
|
11
|
-
|
12
|
-
|
13
|
-
# @return [{Symbol => Object}] the content value hash at the point of the last
|
14
|
-
# take_snapshot call
|
11
|
+
# @return [{Symbol => Object}] the content value hash at the point of the last {#take_snapshot}
|
12
|
+
# call
|
15
13
|
attr_reader :snapshot
|
16
14
|
|
17
15
|
# @param [Resource, <Resource>, nil] obj the object(s) to check
|
@@ -30,10 +28,17 @@ module CaRuby
|
|
30
28
|
not saved?(obj)
|
31
29
|
end
|
32
30
|
|
33
|
-
#
|
34
|
-
#
|
31
|
+
# Returns the data access mediator for this domain object, if any. The default implementation
|
32
|
+
# returns nil. Application #{Resource} modules can override this method.
|
33
|
+
#
|
34
|
+
# @return [Database, nil] the data access mediator for this Persistable, if any
|
35
35
|
def database
|
36
|
-
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [PersistenceService, nil] the database application service for this Persistable, if any
|
40
|
+
def persistence_service
|
41
|
+
database.persistence_service(self.class) if database
|
37
42
|
end
|
38
43
|
|
39
44
|
# Fetches the domain objects which match this template from the {#database}.
|
@@ -177,13 +182,12 @@ module CaRuby
|
|
177
182
|
def add_lazy_loader(loader, attributes=nil)
|
178
183
|
# guard against invalid call
|
179
184
|
if identifier.nil? then raise ValidationError.new("Cannot add lazy loader to an unfetched domain object: #{self}") end
|
180
|
-
|
181
185
|
# the attributes to lazy-load
|
182
186
|
attributes ||= loadable_attributes
|
183
187
|
return if attributes.empty?
|
184
188
|
# define the reader and writer method overrides for the missing attributes
|
185
|
-
|
186
|
-
logger.debug { "Lazy loader added to #{qp} attributes #{
|
189
|
+
attrs = attributes.select { |attr| inject_lazy_loader(attr) }
|
190
|
+
logger.debug { "Lazy loader added to #{qp} attributes #{attrs.to_series}." } unless attrs.empty?
|
187
191
|
end
|
188
192
|
|
189
193
|
# Returns the attributes to load on demand. The base attribute list is given by the
|
@@ -361,7 +365,6 @@ module CaRuby
|
|
361
365
|
vh = @snapshot
|
362
366
|
ovh = value_hash(self.class.updatable_attributes)
|
363
367
|
|
364
|
-
|
365
368
|
# KLUDGE TODO - confirm this is still a problem and fix
|
366
369
|
# In Galena frozen migration example, SpecimenPosition snapshot doesn't include identifier; work around this here
|
367
370
|
# This could be related to the problem of an abstract DomainObject not being added as a domain module class. See the
|
@@ -371,8 +374,6 @@ module CaRuby
|
|
371
374
|
end
|
372
375
|
# END OF KLUDGE
|
373
376
|
|
374
|
-
|
375
|
-
|
376
377
|
if vh.size < ovh.size then
|
377
378
|
attr, oval = ovh.detect { |a, v| not vh.has_key?(a) }
|
378
379
|
logger.debug { "#{qp} is missing snapshot #{attr} compared to the current value #{oval.qp}." }
|
@@ -400,7 +401,7 @@ module CaRuby
|
|
400
401
|
# @return [Boolean] whether a loader was added to the attribute
|
401
402
|
def inject_lazy_loader(attribute)
|
402
403
|
# bail if there is already a value
|
403
|
-
|
404
|
+
return false if attribute_loaded?(attribute)
|
404
405
|
# the accessor methods to modify
|
405
406
|
reader, writer = self.class.attribute_metadata(attribute).accessors
|
406
407
|
# The singleton attribute reader method loads the reference once and thenceforth calls the
|
@@ -411,6 +412,15 @@ module CaRuby
|
|
411
412
|
instance_eval "def #{writer}(value); remove_lazy_loader(:#{attribute}); super; end"
|
412
413
|
true
|
413
414
|
end
|
415
|
+
|
416
|
+
# @param (see #inject_lazy_loader)
|
417
|
+
# @return [Boolean] whether the attribute references one or more domain objects, and each
|
418
|
+
# referenced object has an identifier
|
419
|
+
def attribute_loaded?(attribute)
|
420
|
+
value = transient_value(attribute)
|
421
|
+
return false if value.nil_or_empty?
|
422
|
+
Enumerable === value ? value.all? { |ref| ref.identifier } : value.identifier
|
423
|
+
end
|
414
424
|
|
415
425
|
# Loads the reference attribute database value into this Persistable.
|
416
426
|
#
|
@@ -419,14 +429,12 @@ module CaRuby
|
|
419
429
|
def load_reference(attribute)
|
420
430
|
ldr = database.lazy_loader
|
421
431
|
# bypass the singleton method and call the class instance method if the lazy loader is disabled
|
422
|
-
unless ldr.enabled?
|
423
|
-
return self.class.instance_method(attribute).bind(self).call
|
424
|
-
end
|
432
|
+
return transient_value(attribute) unless ldr.enabled?
|
425
433
|
|
426
434
|
# Disable lazy loading first for the attribute, since the reader method is called by the loader.
|
427
435
|
remove_lazy_loader(attribute)
|
428
436
|
# load the fetched value
|
429
|
-
merged = ldr.
|
437
|
+
merged = ldr.load(self, attribute)
|
430
438
|
|
431
439
|
# update dependent snapshots if necessary
|
432
440
|
attr_md = self.class.attribute_metadata(attribute)
|
@@ -447,6 +455,12 @@ module CaRuby
|
|
447
455
|
|
448
456
|
merged
|
449
457
|
end
|
458
|
+
|
459
|
+
# @param (see #load_reference)
|
460
|
+
# @return the in-memory attribute value, without invoking the lazy loader
|
461
|
+
def transient_value(attribute)
|
462
|
+
self.class.instance_method(attribute).bind(self).call
|
463
|
+
end
|
450
464
|
|
451
465
|
# Disables the given singleton attribute accessor method.
|
452
466
|
#
|
@@ -2,9 +2,16 @@ require 'caruby/util/version'
|
|
2
2
|
require 'caruby/database'
|
3
3
|
require 'caruby/util/stopwatch'
|
4
4
|
|
5
|
-
import 'gov.nih.nci.common.util.HQLCriteria'
|
6
|
-
|
7
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
|
+
|
8
15
|
# A PersistenceService wraps a caCORE application service.
|
9
16
|
class PersistenceService
|
10
17
|
# The service name.
|
@@ -17,14 +24,15 @@ module CaRuby
|
|
17
24
|
#
|
18
25
|
# @param [String] the caBIG application service name
|
19
26
|
# @param [{Symbol => Object}] opts the options
|
20
|
-
# @option opts :host the service host (default +localhost+)
|
21
|
-
# @option opts :version the caTissue version identifier
|
27
|
+
# @option opts [String] :host the service host (default +localhost+)
|
28
|
+
# @option opts [String] :version the caTissue version identifier
|
22
29
|
def initialize(name, opts={})
|
23
30
|
@name = name
|
24
31
|
ver_opt = opts[:version]
|
25
32
|
@version = ver_opt.to_s.to_version if ver_opt
|
26
33
|
@host = opts[:host] || default_host
|
27
34
|
@port = opts[:port] || 8080
|
35
|
+
@url = "http://#{@host}:#{@port}/#{@name}/http/remoteService"
|
28
36
|
@timer = Stopwatch.new
|
29
37
|
logger.debug { "Created persistence service #{name} at #{@host}:#{@port}." }
|
30
38
|
end
|
@@ -81,11 +89,16 @@ module CaRuby
|
|
81
89
|
end
|
82
90
|
end
|
83
91
|
|
92
|
+
# Returns a freshly initialized ApplicationServiceProvider remote instance.
|
93
|
+
#
|
94
|
+
# caCORE alert - 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
|
+
#
|
84
98
|
# @return [ApplicationServiceProvider] the CaCORE service provider wrapped by this PersistenceService
|
85
99
|
def app_service
|
86
|
-
|
87
|
-
|
88
|
-
ApplicationServiceProvider.remote_instance(url)
|
100
|
+
logger.debug { "Connecting to service provider at #{@url}..." }
|
101
|
+
ApplicationServiceProvider.remote_instance(@url)
|
89
102
|
end
|
90
103
|
|
91
104
|
private
|
@@ -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 = SearchTemplateBuilder.new
|
17
17
|
# the fetch result matcher
|
18
18
|
@matcher = FetchedMatcher.new
|
19
19
|
# the fetched copier
|
@@ -85,16 +85,18 @@ module CaRuby
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
# Returns whether domain object
|
89
|
-
# This method fetches
|
90
|
-
#
|
88
|
+
# Returns whether the given domain object has a database identifier or exists in the database.
|
89
|
+
# This method fetches the object from the database if necessary.
|
90
|
+
#
|
91
|
+
# @param [Resource, <Resource>] obj the domain object(s) to find
|
92
|
+
# @return [Boolean] whether the domain object(s) exist in the database
|
91
93
|
def exists?(obj)
|
92
94
|
if obj.nil? then
|
93
95
|
false
|
94
96
|
elsif obj.collection? then
|
95
97
|
obj.all? { |item| exists?(item) }
|
96
98
|
else
|
97
|
-
obj.identifier or
|
99
|
+
obj.identifier or find(obj)
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
@@ -196,9 +198,8 @@ module CaRuby
|
|
196
198
|
def query_hql(hql)
|
197
199
|
java_name = hql[/from\s+(\S+)/i, 1]
|
198
200
|
raise DatabaseError.new("Could not determine target type from HQL: #{hql}") if java_name.nil?
|
199
|
-
|
200
|
-
|
201
|
-
service.query(hql)
|
201
|
+
tgt = Class.to_ruby(java_name)
|
202
|
+
persistence_service(tgt).query(hql)
|
202
203
|
end
|
203
204
|
|
204
205
|
# Returns an array of objects fetched from the database which matches
|
@@ -229,9 +230,9 @@ module CaRuby
|
|
229
230
|
# Returns an array of objects fetched from the database which matches
|
230
231
|
# the given template and follows the given optional domain attribute.
|
231
232
|
def query_on_template(template, attribute=nil)
|
232
|
-
|
233
|
-
|
234
|
-
attribute ?
|
233
|
+
tgt = attribute ? template.class.domain_type(attribute) : template.class
|
234
|
+
svc = persistence_service(tgt)
|
235
|
+
attribute ? svc.query(template, attribute) : svc.query(template)
|
235
236
|
end
|
236
237
|
|
237
238
|
# Queries on the given template and attribute by issuing a HQL query with an identifier condition.
|
@@ -272,6 +273,10 @@ module CaRuby
|
|
272
273
|
|
273
274
|
# Queries the given query object attribute by querying an attribute type template which references obj.
|
274
275
|
#
|
276
|
+
# caCORE alert - caCORE caCORE search enters an infinite loop when the search argument has an object
|
277
|
+
# reference graph cycle. Work-around is to ensure that reference integrity is broken in the search
|
278
|
+
# argument by not setting inverse attributes.
|
279
|
+
#
|
275
280
|
# @param (see #query_object)
|
276
281
|
def query_with_inverted_reference(obj, attribute=nil)
|
277
282
|
attr_md = obj.class.attribute_metadata(attribute)
|
@@ -282,17 +287,15 @@ module CaRuby
|
|
282
287
|
tmpl = attr_md.type.new
|
283
288
|
# the inverse attribute
|
284
289
|
inv_md = tmpl.class.attribute_metadata(attr_md.inverse)
|
285
|
-
#
|
286
|
-
#
|
290
|
+
# The Java property writer to set the tmpl inverse to ref.
|
291
|
+
# Use the property writer rather than the attribute writer in order to curtail automatically
|
287
292
|
# adding tmpl to the ref attribute value when the inv_md attribute is set to ref.
|
288
|
-
|
289
|
-
# enters an infinite loop upon encountering an object graph cycle.
|
290
|
-
writer = inv_md.property_accessors.last
|
293
|
+
wtr = inv_md.property_writer
|
291
294
|
# parameterize tmpl with inverse ref
|
292
|
-
tmpl.send(
|
295
|
+
tmpl.send(wtr, ref)
|
293
296
|
# submit the query
|
294
297
|
logger.debug { "Submitting #{obj.qp} #{attribute} inverted query template #{tmpl.qp} ..." }
|
295
|
-
persistence_service(tmpl).query(tmpl)
|
298
|
+
persistence_service(tmpl.class).query(tmpl)
|
296
299
|
end
|
297
300
|
|
298
301
|
# Finds the database content matching the given search object and merges the matching
|
@@ -310,7 +313,7 @@ module CaRuby
|
|
310
313
|
# @raise [DatabaseError] if more than object matches the obj attribute values or if
|
311
314
|
# the search object is a dependent entity that does not reference an owner
|
312
315
|
def find_object(obj)
|
313
|
-
|
316
|
+
if @transients.include?(obj) then
|
314
317
|
logger.debug { "Find #{obj.qp} obviated since the search was previously unsuccessful in the current database operation context." }
|
315
318
|
return
|
316
319
|
end
|
@@ -327,7 +330,6 @@ module CaRuby
|
|
327
330
|
# so it is done manually here.
|
328
331
|
# recursively copy the nondomain attributes, esp. the identifer, of the fetched domain object references
|
329
332
|
merge_fetched(fetched, obj)
|
330
|
-
|
331
333
|
# caCORE alert - see query method alerts.
|
332
334
|
# Inject the lazy loader for loadable domain reference attributes.
|
333
335
|
persistify(obj, fetched)
|
@@ -354,7 +356,6 @@ module CaRuby
|
|
354
356
|
# submit the query on the template
|
355
357
|
logger.debug { "Query template for finding #{obj.qp}: #{template}." }
|
356
358
|
result = query_on_template(template)
|
357
|
-
|
358
359
|
# a fetch query which returns more than one result is an error.
|
359
360
|
# possible cause is an incorrect secondary key.
|
360
361
|
if result.size > 1 then
|