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.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'caruby/util/log'
|
2
|
-
require 'caruby/util/pretty_print'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
# SearchTemplateBuilder builds a template suitable for a caCORE saarch database operation.
|
6
|
-
class SearchTemplateBuilder
|
7
|
-
# Returns a template for matching the domain object obj and the optional hash values.
|
8
|
-
# The default hash attributes are the {Domain::Attributes#searchable_attributes}.
|
9
|
-
# The template includes only the non-domain attributes of the hash references.
|
10
|
-
#
|
11
|
-
# @quirk caCORE Because of caCORE API limitations, the obj searchable attribute
|
12
|
-
# values are limited to the following:
|
13
|
-
# * non-domain attribute values
|
14
|
-
# * non-collection domain attribute references which contain a key
|
15
|
-
#
|
16
|
-
# @quirk caCORE the caCORE query builder breaks on reference cycles and is easily confused
|
17
|
-
# by extraneous references, so it is necessary to search with a template instead that contains
|
18
|
-
# only references essential to the search. Each reference is confirmed to exist and the
|
19
|
-
# reference content in the template consists entirely of the fetched identifier attribute.
|
20
|
-
def build_template(obj, hash=nil)
|
21
|
-
# split the attributes into reference and non-reference attributes.
|
22
|
-
# the new search template object is built from the non-reference attributes.
|
23
|
-
# the reference attributes values are copied and added.
|
24
|
-
logger.debug { "Building search template for #{obj.qp}..." }
|
25
|
-
hash ||= obj.value_hash(obj.class.searchable_attributes)
|
26
|
-
# the searchable attribute => value hash
|
27
|
-
rh, nrh = hash.split { |attr, value| Resource === value }
|
28
|
-
# make the search template from the non-reference attributes
|
29
|
-
tmpl = obj.class.new.merge_attributes(nrh)
|
30
|
-
# get references for the search template
|
31
|
-
unless rh.empty? then
|
32
|
-
logger.debug { "Collecting search reference parameters for #{obj.qp} from attributes #{rh.keys.to_series}..." }
|
33
|
-
end
|
34
|
-
rh.each { |attr, ref| add_search_template_reference(tmpl, ref, attr) }
|
35
|
-
tmpl
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
# Sets the template attribute to a new search reference object created from the given
|
41
|
-
# source domain object. The reference contains only the source identifier, if it exists,
|
42
|
-
# or the source non-domain attributes otherwise.
|
43
|
-
#
|
44
|
-
# @return [Resource] the search reference
|
45
|
-
def add_search_template_reference(template, source, attribute)
|
46
|
-
ref = source.identifier ? source.copy(:identifier) : source.copy
|
47
|
-
# Disable inverse integrity, since the template attribute assignment might have added a reference
|
48
|
-
# from ref to template, which introduces a template => ref => template cycle that causes a caCORE
|
49
|
-
# search infinite loop. Use the Java property writer instead.
|
50
|
-
wtr = template.class.attribute_metadata(attribute).property_writer
|
51
|
-
template.send(wtr, ref)
|
52
|
-
logger.debug { "Search reference parameter #{attribute} for #{template.qp} set to #{ref} copied from #{source.qp}" }
|
53
|
-
ref
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,278 +0,0 @@
|
|
1
|
-
require 'caruby/domain/reference_visitor'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
# StoreTemplateBuilder creates a template suitable for a create or update database operation.
|
5
|
-
class StoreTemplateBuilder
|
6
|
-
# Creates a new StoreTemplateBuilder for the given database. The attributes to merge into
|
7
|
-
# the template are determined by the block given to this initializer, filtered as follows:
|
8
|
-
# * If the save operation is a create, then exclude the auto-generated attributes.
|
9
|
-
# * If the visited object has an identifier, then include only those attributes
|
10
|
-
# which {Domain::Attribute#cascade_update_to_create?} or have an identifier.
|
11
|
-
#
|
12
|
-
# @param [Database] database the target database
|
13
|
-
# @yield [ref] the required selector block which determines which attributes are copied into the template
|
14
|
-
# @yieldparam [Resource] ref the domain object to copy
|
15
|
-
def initialize(database)
|
16
|
-
@database = database
|
17
|
-
unless block_given? then
|
18
|
-
raise ArgumentError.new("StoreTemplateBuilder is missing the required template copy attribute selector block")
|
19
|
-
end
|
20
|
-
|
21
|
-
# the mergeable attributes filter the given block with exclusions
|
22
|
-
@mergeable = Proc.new { |obj| mergeable_attributes(obj, yield(obj)) }
|
23
|
-
# the storable prerequisite reference visitor
|
24
|
-
@prereq_vstr = ReferenceVisitor.new(:prune_cycle) { |ref| savable_template_attributes(ref) }
|
25
|
-
|
26
|
-
# the savable attributes filter the given block with exclusions
|
27
|
-
savable = Proc.new { |obj| savable_attributes(obj, yield(obj)) }
|
28
|
-
# the domain attributes to copy is determined by the constructor caller
|
29
|
-
# @quirk caTissue must copy all of the non-domain attributes rather than just the identifier,
|
30
|
-
# since caTissue auto-generated Specimen update requires the parent collection status. This
|
31
|
-
# is the only known occurrence of a referenced object required non-identifier attribute.
|
32
|
-
# The copy attributes are parameterized by the top-level save target.
|
33
|
-
copier = Proc.new do |src|
|
34
|
-
tgt = src.copy
|
35
|
-
logger.debug { "Store template builder copied #{src.qp} into #{tgt}." }
|
36
|
-
copy_proxied_save_references(src, tgt)
|
37
|
-
tgt
|
38
|
-
end
|
39
|
-
# the template copier
|
40
|
-
@copy_vstr = CopyVisitor.new(:mergeable => savable, :copier => copier) { |ref| savable_template_attributes(ref) }
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns a new domain object which serves as the argument for obj create or update.
|
44
|
-
#
|
45
|
-
# This method copies a portion of the obj object graph to a template object.
|
46
|
-
# The template object graph consists of copies of obj object graph which are necessary
|
47
|
-
# to store obj. The template object graph contains only those references which are
|
48
|
-
# essential to the store operation.
|
49
|
-
#
|
50
|
-
# @quirk caCORE +caCORE+ expects the store argument to be carefully prepared prior to
|
51
|
-
# the create or update. build_storable_template culls the target object with a template
|
52
|
-
# which includes only those references which are necessary for the store to succeed.
|
53
|
-
# This template builder ensures that mandatory independent references exist. Cascaded
|
54
|
-
# dependent references are included in the template but are not created before submission
|
55
|
-
# to +caCORE+. These reference attribute distinctions are implicit application rules which
|
56
|
-
# are explicated in the +caRuby+ application domain class definition using Metadata
|
57
|
-
# methods.
|
58
|
-
#
|
59
|
-
# @quirk caCORE +caCORE+ create issues an error if a create argument directly or
|
60
|
-
# indirectly references a non-cascaded domain object without an identifier, even if the
|
61
|
-
# reference is not relevant to the create. The template returned by this method elides
|
62
|
-
# all non-essential references.
|
63
|
-
#
|
64
|
-
# @quirk caCORE application business logic performs unnecessary verification
|
65
|
-
# of uncascaded references as if they were a cascaded create. This can result in
|
66
|
-
# an obscure ApplicationException. The server.log stack trace indicates the
|
67
|
-
# extraneous verification code. For example, +caTissue+ +NewSpecimenBizLogic.validateStorageContainer+
|
68
|
-
# is unnecessarily called on a SpecimenCollectionGroup (SCG) update. SCG does not
|
69
|
-
# cascade to Specimen, but caTissue considers the SCG update a Specimen create
|
70
|
-
# anyway if the SCG references a Specimen without an identifier. The Specimen
|
71
|
-
# business logic then raises an exception when it finds a StorageContainer
|
72
|
-
# without an identifier in the Specimen object graph. Therefore, an update must
|
73
|
-
# build a storable template which prunes the update object graph to exclude uncascaded
|
74
|
-
# objects. These uncascaded objects should be ignored by the application but aren't.
|
75
|
-
#
|
76
|
-
# @param [Resource] obj the domain object to save
|
77
|
-
# @return [Resource] the template to use as the caCORE argument
|
78
|
-
def build_template(obj, autogenerated=false)
|
79
|
-
# set the database operation subject
|
80
|
-
@subject = obj
|
81
|
-
# prepare the object for a store operation
|
82
|
-
ensure_storable(obj)
|
83
|
-
# copy the cascade hierarchy
|
84
|
-
logger.debug { "Building storable template for #{obj.qp}..." }
|
85
|
-
tmpl = @copy_vstr.visit(obj)
|
86
|
-
logger.debug { "Built #{obj.qp} template #{tmpl.qp} by mapping references #{@copy_vstr.matches.qp}" }
|
87
|
-
tmpl
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
# Ensure that the given domain object obj can be created or updated by setting the identifier for
|
93
|
-
# each independent reference in the create template object graph.
|
94
|
-
#
|
95
|
-
# @quirk caCORE +caCORE+ raises an ApplicationException if an independent reference in the create or
|
96
|
-
# update argument does not have an identifier. The +caCORE+ server log error is as follows:
|
97
|
-
# java.lang.IllegalArgumentException: id to load is required for loading
|
98
|
-
# The server log stack trace indicates a bizlogic line that offers a clue to the offending reference.
|
99
|
-
def ensure_storable(obj)
|
100
|
-
# Add defaults, which might introduce independent references. Enable the lazy loader to fetch
|
101
|
-
# create references from the database where needed to build defaults.
|
102
|
-
obj.add_defaults
|
103
|
-
# create the prerequisite references if necessary
|
104
|
-
prereqs = collect_prerequisites(obj)
|
105
|
-
unless prereqs.empty? then
|
106
|
-
logger.debug { "Ensuring references for #{obj.qp} exist: #{prereqs.map { |ref| ref.qp }.to_series}..." }
|
107
|
-
@database.ensure_exists(prereqs)
|
108
|
-
logger.debug { "Prerequisite references for #{obj.qp} exist: #{prereqs.map { |ref| ref }.to_series}." }
|
109
|
-
end
|
110
|
-
# Verify that the object is complete
|
111
|
-
obj.validate
|
112
|
-
end
|
113
|
-
|
114
|
-
# Returns the attributes to visit in building the template for the given
|
115
|
-
# domain object. The visitable attributes consist of the following:
|
116
|
-
# * The {Domain::Attributes#unproxied_savable_template_attributes} filtered as follows:
|
117
|
-
# * If the database operation is a create, then exclude the cascaded attributes.
|
118
|
-
# * If the given object has an identifier, then exclude the attributes which
|
119
|
-
# have the the :no_cascade_update_to_create flag set.
|
120
|
-
# * The {Domain::Attributes#proxied_savable_template_attributes} are included if and
|
121
|
-
# only if every referenced object has an identifier, and therefore does not
|
122
|
-
# need to be proxied.
|
123
|
-
#
|
124
|
-
# @quirk caTissue caTissue ignores some references, e.g. Participant CPR, and auto-generates
|
125
|
-
# the values instead. Therefore, the create template builder excludes these auto-generated
|
126
|
-
# attributes. After the create, the auto-generated references are merged into the created
|
127
|
-
# object graph and the references are updated if necessary.
|
128
|
-
#
|
129
|
-
# @param [Resource] obj the domain object copied to the update template
|
130
|
-
# @return [<Symbol>] the reference attributes to include in the update template
|
131
|
-
def savable_template_attributes(obj)
|
132
|
-
# The starting set of candidate attributes is the unproxied cascaded references.
|
133
|
-
unproxied = savable_attributes(obj, obj.class.unproxied_savable_template_attributes)
|
134
|
-
# The proxied attributes to save.
|
135
|
-
proxied = savable_proxied_attributes(obj)
|
136
|
-
# The combined set of savable attributes
|
137
|
-
proxied.empty? ? unproxied : unproxied + proxied
|
138
|
-
end
|
139
|
-
|
140
|
-
# Filters the given attributes, if necessary, to exclude attributes as follows:
|
141
|
-
# * If the save operation is a create, then exclude the
|
142
|
-
# {Domain::Attribute#autogenerated_on_create?} attributes.
|
143
|
-
#
|
144
|
-
# @param [Resource] obj the visited domain object
|
145
|
-
# @param [Attributes::Filter] the savable attribute filter
|
146
|
-
# @return [Attributes::Filter] the composed attribute filter
|
147
|
-
def mergeable_attributes(obj, attributes)
|
148
|
-
# If this is an update, then there is no filter on the given attributes.
|
149
|
-
return attributes if @subject.identifier
|
150
|
-
# This is a create: ignore the optional auto-generated attributes.
|
151
|
-
mas = obj.mandatory_attributes.to_set
|
152
|
-
attributes.compose { |attr_md| mas.include?(attr_md.to_sym) or not attr_md.autogenerated_on_create? }
|
153
|
-
end
|
154
|
-
|
155
|
-
# Composes the given attributes, if necessary, to exclude attributes as follows:
|
156
|
-
# * If the save operation is a create, then exclude the auto-generated attributes.
|
157
|
-
# * If the visited object has an identifier, then include only those attributes
|
158
|
-
# which {Domain::Attribute#cascade_update_to_create?} or have an identifier.
|
159
|
-
#
|
160
|
-
# @param (see #mergeable_attributes)
|
161
|
-
# @return (see #mergeable_attributes)
|
162
|
-
def savable_attributes(obj, attributes)
|
163
|
-
mgbl = mergeable_attributes(obj, attributes)
|
164
|
-
return mgbl if obj.identifier.nil?
|
165
|
-
# The currently visited object is an update: include attributes which
|
166
|
-
# either cascade update to create or have saved references.
|
167
|
-
mgbl.compose do |attr_md|
|
168
|
-
attr_md.cascade_update_to_create? or Persistable.saved?(obj.send(attr_md.to_sym))
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Returns the proxied attributes to save. A proxied attribute is included only if the proxied
|
173
|
-
# dependents have an identifier, since those without an identifer are created separately via
|
174
|
-
# the proxy.
|
175
|
-
#
|
176
|
-
# @param [Resource] obj the visited domain object
|
177
|
-
# @return [<Attribute>] the proxied cascaded attributes with an unsaved reference
|
178
|
-
def savable_proxied_attributes(obj)
|
179
|
-
# Include a proxied reference only if the proxied dependents have an identifier,
|
180
|
-
# since those without an identifer are created separately via the proxy.
|
181
|
-
obj.class.proxied_savable_template_attributes.reject do |attr|
|
182
|
-
ref = obj.send(attr)
|
183
|
-
case ref
|
184
|
-
when Enumerable then ref.any? { |dep| not dep.identifier }
|
185
|
-
when Resource then not ref.identifier
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# Copies proxied references as needed.
|
191
|
-
#
|
192
|
-
# @quirk caTissue even though Specimen save cascades to SpecimenPosition,
|
193
|
-
# SpecimenPosition cannot be updated directly. Rather than simply not
|
194
|
-
# cascading to the SpecimenPosition, caTissue checks a Specimen save argument
|
195
|
-
# to ensure that the SpecimenPosition reflects the current database state
|
196
|
-
# rather than the desired cascaded state. Play along with this bizarre
|
197
|
-
# mechanism by adding our own bizarre work-around mechanism to copy a
|
198
|
-
# proxied reference only if it has an identifier. This works only because
|
199
|
-
# another work-around in the #{Writer} updates proxied
|
200
|
-
# references via the proxy create before building the update template.
|
201
|
-
def copy_proxied_save_references(obj, template)
|
202
|
-
return unless obj.identifier
|
203
|
-
obj.class.proxied_savable_template_attributes.each do |attr|
|
204
|
-
# the proxy source
|
205
|
-
ref = obj.send(attr)
|
206
|
-
case ref
|
207
|
-
when Enumerable then
|
208
|
-
# recurse on the source collection
|
209
|
-
coll = template.send(attr)
|
210
|
-
ref.each do |dep|
|
211
|
-
copy = copy_proxied_save_reference(obj, attr, template, dep)
|
212
|
-
coll << copy if copy
|
213
|
-
end
|
214
|
-
when Resource then
|
215
|
-
# copy the source
|
216
|
-
copy = copy_proxied_save_reference(obj, attr, template, ref)
|
217
|
-
# set the attribute to the copy
|
218
|
-
template.set_attribute(attr, copy) if copy
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Copies a proxied reference.
|
224
|
-
#
|
225
|
-
# @return [Resource, nil] the copy, or nil if no copy is made
|
226
|
-
def copy_proxied_save_reference(obj, attribute, template, proxied)
|
227
|
-
# only copy an existing proxied
|
228
|
-
return unless proxied.identifier
|
229
|
-
# the proxied attribute => value hash
|
230
|
-
vh = proxied.value_hash
|
231
|
-
# map references to either the copied owner or a new copy of the reference
|
232
|
-
tvh = vh.transform { |value| Resource === value ? (value == obj ? template : value.copy) : value }
|
233
|
-
# the copy with the adjusted values
|
234
|
-
copy = proxied.class.new.merge_attributes(tvh)
|
235
|
-
logger.debug { "Created #{obj.qp} proxied #{attribute} save template copy #{proxied.pp_s}." }
|
236
|
-
copy
|
237
|
-
end
|
238
|
-
|
239
|
-
# @param [Resource] obj the domain object to store
|
240
|
-
# @return [<Resource>] the references which must be created in order to store the object
|
241
|
-
def collect_prerequisites(obj)
|
242
|
-
prereqs = Set.new
|
243
|
-
# visit the cascaded attributes
|
244
|
-
@prereq_vstr.visit(obj) do |stbl|
|
245
|
-
# Check each mergeable attribute for prerequisites. The mergeable attributes includes
|
246
|
-
# both cascaded and independent attributes. The selection block filters for independent
|
247
|
-
# domain objects which don't have an identifier.
|
248
|
-
@mergeable.call(stbl).each_pair do |attr, attr_md|
|
249
|
-
# Cascaded attributes are not prerequisite, since they are created when the owner is created.
|
250
|
-
# Note that each non-prerequisite cascaded reference is still visited in order to ensure
|
251
|
-
# that each independent object referenced by a cascaded reference is recognized as a
|
252
|
-
# candidate prerequisite.
|
253
|
-
next if attr_md.cascaded?
|
254
|
-
# add qualified prerequisite attribute references
|
255
|
-
stbl.send(attr).enumerate do |ref|
|
256
|
-
# Add the prerequisite if it satisfies the prerequisite? condition.
|
257
|
-
prereqs << ref if prerequisite?(ref, obj, attr)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
prereqs
|
262
|
-
end
|
263
|
-
|
264
|
-
# A referenced object is a target object save prerequisite if none of the follwing is true:
|
265
|
-
# * it is the target object
|
266
|
-
# * it was already created
|
267
|
-
# * it is in an immediate or recursive dependent of the target object
|
268
|
-
# * the current save operation is in the context of creating the referenced object
|
269
|
-
#
|
270
|
-
# @param [Resource] ref the reference to check
|
271
|
-
# @param [Resource] obj the object being stored
|
272
|
-
# @param [Symbol] attribute the reference attribute
|
273
|
-
# @return [Boolean] whether the reference should exist before storing the object
|
274
|
-
def prerequisite?(ref, obj, attribute)
|
275
|
-
not (ref == obj or ref.identifier or ref.owner_ancestor?(obj))
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
data/lib/caruby/domain.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'caruby/util/collection'
|
3
|
-
require 'caruby/util/log'
|
4
|
-
require 'caruby/domain/importer'
|
5
|
-
|
6
|
-
module CaRuby
|
7
|
-
class JavaImportError < StandardError; end;
|
8
|
-
|
9
|
-
# The application and database connection access command line options.
|
10
|
-
ACCESS_OPTS = [
|
11
|
-
[:user, "--user USER", "the application login user"],
|
12
|
-
[:password, "--password PSWD", "the application login password"],
|
13
|
-
[:host, "--host HOST", "the application host name"],
|
14
|
-
[:port, "--port PORT", "the application port number"],
|
15
|
-
[:classpath, "--classpath PATH", "the application client classpath"],
|
16
|
-
[:database_host, "--database_host HOST", "the database host name"],
|
17
|
-
[:database_type, "--database_type TYPE", "the database type (mysql or oracle)"],
|
18
|
-
[:database_driver, "--database_driver DRIVER", "the database driver string"],
|
19
|
-
[:database_driver_class, "--database_driver_class CLASS", "the database driver class name"],
|
20
|
-
[:database_port, "--database_port PORT", Integer, "the database port number"],
|
21
|
-
[:database, "--database NAME", "the database name"],
|
22
|
-
[:database_user, "--database_user USER", "the database login user"],
|
23
|
-
[:database_password, "--database_password PSWD", "the database login password"]
|
24
|
-
]
|
25
|
-
|
26
|
-
# Domain extends a Module with Java class {Metadata} support.
|
27
|
-
#
|
28
|
-
# A Java class is imported into Ruby either by including the given Resource module
|
29
|
-
# or by referenceing the class name for the first time. For example, the
|
30
|
-
# +ClinicalTrials+ wrapper for Java package +org.nci.ctms+ classes and
|
31
|
-
# Ruby class definitions in the +domain+ subdirectory is enabled as follows:
|
32
|
-
# module ClinicalTrials
|
33
|
-
# PKG = 'org.nci.ctms'
|
34
|
-
# SRC_DIR = File.join(File.dirname(__FILE__), 'domain')
|
35
|
-
# CaRuby::Domain.extend_module(self, :package => PKG, :directory => SRC_DIR)
|
36
|
-
#
|
37
|
-
# The first reference by name to +ClinicalTrials::Subject+ imports the Java class
|
38
|
-
# +org.nci.ctms.Subject+ into +ClinicalTrials+. The +Subject+ Java property meta-data
|
39
|
-
# is introspected and the {Resource} module is included.
|
40
|
-
module Domain
|
41
|
-
# Extends the given module with importable Java class {Metadata} support.
|
42
|
-
#
|
43
|
-
# @param [Module] mod the module to extend
|
44
|
-
# @param [{Symbol => Object}] opts the extension options
|
45
|
-
# @option opts [Module] :metadata the optional {Metadata} extension (default {Metadata})
|
46
|
-
# @option opts [Module] :mixin the optional mix-in module (default {Resource})
|
47
|
-
# @option opts [String] :package the required Java package name
|
48
|
-
# @option opts [String] :directory the optional directory of source class definitions to load
|
49
|
-
def self.extend_module(mod, opts)
|
50
|
-
mod.extend(self)
|
51
|
-
Importer.extend_module(mod, opts)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Loads the {#access_properties} and adds the +path+ property items to the Java classpath.
|
55
|
-
#
|
56
|
-
# @param [Module] mod the module to extend
|
57
|
-
def self.extended(mod)
|
58
|
-
super
|
59
|
-
mod.ensure_classpath_defined
|
60
|
-
end
|
61
|
-
|
62
|
-
# Loads the application start-up properties on demand. The properties are defined in the properties
|
63
|
-
# file or as environment variables.
|
64
|
-
# The properties file path is a period followed by the lower-case application name in the home directory,
|
65
|
-
# e.g. +~/.clincaltrials+ for the +ClinicalTrials+ application.
|
66
|
-
#
|
67
|
-
# The property file format is a series of property definitions in the form _property_: _value_.
|
68
|
-
# The supported properties include the following:
|
69
|
-
# * +host+ - the application server host (default +localhost+)
|
70
|
-
# * +port+ - the application server port (default +8080+)
|
71
|
-
# * +user+ - the application server login
|
72
|
-
# * +password+ - the application service password
|
73
|
-
# * +path+ or +classpath+ - the application client Java directories
|
74
|
-
# * +database+ - the application database name
|
75
|
-
# * +database_user+ - the application database connection userid
|
76
|
-
# * +database_password+ - the application database connection password
|
77
|
-
# * +database_host+ - the application database connection host (default +localhost+)
|
78
|
-
# * +database_type+ - the application database type, + mysql+ or +oracle+ (default +mysql+)
|
79
|
-
# * +database_driver+ - the application database connection driver (default is the database type default)
|
80
|
-
# * +database_port+ - the application database connection port (default is the database type default)
|
81
|
-
#
|
82
|
-
# The +path+ value is one or more directories separated by a semi-colon(;) or colon (:)
|
83
|
-
# Each path directory and all jar files within the directory are added to the caRuby execution
|
84
|
-
# Java classpath.
|
85
|
-
#
|
86
|
-
# Each property has an environment variable counterpart given by
|
87
|
-
#
|
88
|
-
# @return [{Symbol => Object}] the caBIG application access properties
|
89
|
-
def access_properties
|
90
|
-
@rsc_props ||= load_access_properties
|
91
|
-
end
|
92
|
-
|
93
|
-
# Ensures that the application client classpath is defined. The classpath is defined
|
94
|
-
# in the {#access_properties}. This method is called when a module extends this
|
95
|
-
# Domain, before any application Java domain class is imported into JRuby.
|
96
|
-
def ensure_classpath_defined
|
97
|
-
# Loading the access properties on demand sets the classpath.
|
98
|
-
access_properties
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
# Loads the application start-up properties in the given file path.
|
104
|
-
#
|
105
|
-
# @return (see #access_properties)
|
106
|
-
def load_access_properties
|
107
|
-
# the properties file
|
108
|
-
file = default_properties_file
|
109
|
-
# the access properties
|
110
|
-
props = file && File.exists?(file) ? load_properties_file(file) : {}
|
111
|
-
# Look for environment overrides preceded by the uppercase module name,
|
112
|
-
# e.g. CATISSUE_USER for the CaTissue module.
|
113
|
-
load_environment_properties(props)
|
114
|
-
|
115
|
-
# load the Java application jar path
|
116
|
-
path = props[:classpath] || props[:path]
|
117
|
-
if path then
|
118
|
-
logger.info("Defining application classpath #{path}...")
|
119
|
-
Java.add_path(path)
|
120
|
-
end
|
121
|
-
|
122
|
-
props
|
123
|
-
end
|
124
|
-
|
125
|
-
def load_properties_file(file)
|
126
|
-
props = {}
|
127
|
-
logger.info("Loading application properties from #{file}...")
|
128
|
-
File.open(file).map do |line|
|
129
|
-
# match the tolerant property definition
|
130
|
-
match = PROP_DEF_REGEX.match(line.chomp.strip) || next
|
131
|
-
# the property [name, value] tokens
|
132
|
-
tokens = match.captures
|
133
|
-
pname = tokens.first.to_sym
|
134
|
-
# path is deprecated
|
135
|
-
name = pname == :path ? :classpath : pname
|
136
|
-
value = tokens.last
|
137
|
-
# capture the property
|
138
|
-
props[name] = value
|
139
|
-
end
|
140
|
-
props
|
141
|
-
end
|
142
|
-
|
143
|
-
def load_environment_properties(props)
|
144
|
-
ACCESS_OPTS.each do |spec|
|
145
|
-
# the access option symbol is the first specification item
|
146
|
-
opt = spec[0]
|
147
|
-
# the envvar value
|
148
|
-
value = environment_property(opt) || next
|
149
|
-
# override the file property with the envar value
|
150
|
-
props[opt] = value
|
151
|
-
logger.info("Set application property #{opt} from environment variable #{ev}.")
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# @param [Symbol] opt the property symbol, e.g. :user
|
156
|
-
# @return [String, nil] the value of the corresponding environment variable, e.g. +CATISSUE_USER+
|
157
|
-
def environment_property(opt)
|
158
|
-
@env_prefix ||= name.gsub('::', '_').upcase
|
159
|
-
ev = "#{@env_prefix}_#{opt.to_s.upcase}"
|
160
|
-
value = ENV[ev]
|
161
|
-
# If no classpath envvar, then try the deprecated path envvar.
|
162
|
-
if value.nil? and opt == :classpath then
|
163
|
-
environment_property(:path)
|
164
|
-
else
|
165
|
-
value
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# The property/value matcher, e.g.:
|
170
|
-
# host: jacardi
|
171
|
-
# host = jacardi
|
172
|
-
# host=jacardi
|
173
|
-
# name: J. Edgar Hoover
|
174
|
-
# but not:
|
175
|
-
# # host: jacardi
|
176
|
-
# The captures are the trimmed property and value.
|
177
|
-
PROP_DEF_REGEX = /^(\w+)(?:\s*[:=]\s*)([^#]+)/
|
178
|
-
|
179
|
-
# @return [String] the default application properties file, given by +~/.+_name_,
|
180
|
-
# where _name_ is the underscore unqualified module name, e.g. +~/.catissue+
|
181
|
-
# for module +CaTissue+
|
182
|
-
def default_properties_file
|
183
|
-
home = ENV['HOME'] || ENV['USERPROFILE'] || '~'
|
184
|
-
file = File.expand_path("#{home}/.#{name[/\w+$/].downcase}")
|
185
|
-
if File.exists?(file) then
|
186
|
-
file
|
187
|
-
else
|
188
|
-
logger.warn("The default #{name} application property file was not found: #{file}.")
|
189
|
-
nil
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|