caruby-core 1.4.7 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|