caruby-tissue 1.2.1
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 +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/bin/crtdump +31 -0
- data/bin/crtexample +18 -0
- data/bin/crtextract +47 -0
- data/bin/crtmigrate +17 -0
- data/bin/crtsmoke +27 -0
- data/examples/galena/README.md +53 -0
- data/examples/galena/bin/migrate.rb +42 -0
- data/examples/galena/bin/seed.rb +43 -0
- data/examples/galena/conf/extract/simple_fields.yaml +4 -0
- data/examples/galena/conf/migration/filter_fields.yaml +7 -0
- data/examples/galena/conf/migration/filter_migration.yaml +9 -0
- data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
- data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
- data/examples/galena/conf/migration/general_fields.yaml +42 -0
- data/examples/galena/conf/migration/general_migration.yaml +9 -0
- data/examples/galena/conf/migration/simple_fields.yaml +30 -0
- data/examples/galena/conf/migration/simple_migration.yaml +7 -0
- data/examples/galena/conf/migration/small_fields.yaml +24 -0
- data/examples/galena/conf/migration/small_migration.yaml +9 -0
- data/examples/galena/data/filter.csv +1 -0
- data/examples/galena/data/frozen.csv +1 -0
- data/examples/galena/data/general.csv +1 -0
- data/examples/galena/data/minimal.csv +1 -0
- data/examples/galena/data/simple.csv +1 -0
- data/examples/galena/data/small.csv +1 -0
- data/examples/galena/doc/CaTissue.html +93 -0
- data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
- data/examples/galena/doc/CaTissue/Participant.html +241 -0
- data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
- data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
- data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
- data/examples/galena/doc/Galena.html +290 -0
- data/examples/galena/doc/Galena/Seed.html +203 -0
- data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
- data/examples/galena/doc/_index.html +188 -0
- data/examples/galena/doc/class_list.html +36 -0
- data/examples/galena/doc/css/common.css +1 -0
- data/examples/galena/doc/css/full_list.css +53 -0
- data/examples/galena/doc/css/style.css +307 -0
- data/examples/galena/doc/file.README.html +108 -0
- data/examples/galena/doc/file_list.html +38 -0
- data/examples/galena/doc/frames.html +13 -0
- data/examples/galena/doc/index.html +108 -0
- data/examples/galena/doc/js/app.js +202 -0
- data/examples/galena/doc/js/full_list.js +149 -0
- data/examples/galena/doc/js/jquery.js +154 -0
- data/examples/galena/doc/method_list.html +179 -0
- data/examples/galena/doc/top-level-namespace.html +112 -0
- data/examples/galena/lib/README.html +33 -0
- data/examples/galena/lib/galena.rb +8 -0
- data/examples/galena/lib/galena/cli/seed.rb +43 -0
- data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
- data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
- data/examples/galena/lib/galena/seed/defaults.rb +97 -0
- data/lib/catissue.rb +26 -0
- data/lib/catissue/cli/command.rb +51 -0
- data/lib/catissue/cli/example.rb +31 -0
- data/lib/catissue/cli/migrate.rb +60 -0
- data/lib/catissue/cli/smoke.rb +45 -0
- data/lib/catissue/database.rb +451 -0
- data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
- data/lib/catissue/database/annotation/annotation_service.rb +79 -0
- data/lib/catissue/database/annotation/annotator.rb +84 -0
- data/lib/catissue/database/annotation/entity_manager.rb +10 -0
- data/lib/catissue/database/annotation/integration_service.rb +87 -0
- data/lib/catissue/database/controlled_value_finder.rb +43 -0
- data/lib/catissue/database/controlled_values.rb +162 -0
- data/lib/catissue/domain/abstract_domain_object.rb +8 -0
- data/lib/catissue/domain/abstract_position.rb +22 -0
- data/lib/catissue/domain/abstract_specimen.rb +288 -0
- data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
- data/lib/catissue/domain/address.rb +13 -0
- data/lib/catissue/domain/cancer_research_group.rb +11 -0
- data/lib/catissue/domain/capacity.rb +34 -0
- data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
- data/lib/catissue/domain/collection_event_parameters.rb +13 -0
- data/lib/catissue/domain/collection_protocol.rb +177 -0
- data/lib/catissue/domain/collection_protocol_event.rb +108 -0
- data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
- data/lib/catissue/domain/consent_tier_response.rb +13 -0
- data/lib/catissue/domain/consent_tier_status.rb +29 -0
- data/lib/catissue/domain/container.rb +234 -0
- data/lib/catissue/domain/container_position.rb +21 -0
- data/lib/catissue/domain/container_type.rb +131 -0
- data/lib/catissue/domain/department.rb +13 -0
- data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
- data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
- data/lib/catissue/domain/external_identifier.rb +22 -0
- data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
- data/lib/catissue/domain/institution.rb +13 -0
- data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
- data/lib/catissue/domain/order_details.rb +25 -0
- data/lib/catissue/domain/participant.rb +138 -0
- data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
- data/lib/catissue/domain/password.rb +11 -0
- data/lib/catissue/domain/race.rb +11 -0
- data/lib/catissue/domain/received_event_parameters.rb +25 -0
- data/lib/catissue/domain/scg_event_parameters.rb +11 -0
- data/lib/catissue/domain/site.rb +30 -0
- data/lib/catissue/domain/specimen.rb +456 -0
- data/lib/catissue/domain/specimen_array.rb +47 -0
- data/lib/catissue/domain/specimen_array_content.rb +19 -0
- data/lib/catissue/domain/specimen_array_type.rb +20 -0
- data/lib/catissue/domain/specimen_characteristics.rb +20 -0
- data/lib/catissue/domain/specimen_collection_group.rb +412 -0
- data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
- data/lib/catissue/domain/specimen_position.rb +38 -0
- data/lib/catissue/domain/specimen_protocol.rb +34 -0
- data/lib/catissue/domain/specimen_requirement.rb +143 -0
- data/lib/catissue/domain/storage_container.rb +204 -0
- data/lib/catissue/domain/storage_type.rb +82 -0
- data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
- data/lib/catissue/domain/user.rb +100 -0
- data/lib/catissue/extract/command.rb +31 -0
- data/lib/catissue/extract/delta.rb +62 -0
- data/lib/catissue/extract/extractor.rb +99 -0
- data/lib/catissue/migration/migrator.rb +101 -0
- data/lib/catissue/migration/shims.rb +108 -0
- data/lib/catissue/migration/uniquify.rb +111 -0
- data/lib/catissue/resource.rb +84 -0
- data/lib/catissue/util/controlled_value.rb +29 -0
- data/lib/catissue/util/location.rb +116 -0
- data/lib/catissue/util/log.rb +30 -0
- data/lib/catissue/util/person.rb +31 -0
- data/lib/catissue/util/position.rb +54 -0
- data/lib/catissue/util/storable.rb +34 -0
- data/lib/catissue/util/storage_type_holder.rb +30 -0
- data/lib/catissue/version.rb +7 -0
- metadata +212 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'caruby/database/persistence_service'
|
3
|
+
|
4
|
+
module CaTissue
|
5
|
+
module Annotation
|
6
|
+
# An AnnotatableService queries and saves domain classes which hold annotation attributes.
|
7
|
+
class AnnotatableService < DelegateClass(CaRuby::PersistenceService)
|
8
|
+
def initialize(database, app_service, integration_service)
|
9
|
+
super(app_service)
|
10
|
+
@database = database
|
11
|
+
@integration_service = integration_service
|
12
|
+
end
|
13
|
+
#
|
14
|
+
# def create(obj)
|
15
|
+
# super
|
16
|
+
# # TODO - refactor CaRuby::Database to remove Annotation cases; iterate over each annotation attribute here
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def update(obj)
|
20
|
+
# super
|
21
|
+
# # TODO - refactor CaRuby::Database to remove Annotation cases; iterate over each annotation attribute here
|
22
|
+
# end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'caruby/util/inflector'
|
2
|
+
require 'caruby/database/persistence_service'
|
3
|
+
require 'caruby/util/uniquifier'
|
4
|
+
|
5
|
+
module CaTissue
|
6
|
+
module Annotation
|
7
|
+
# An AnnotationService queries and saves CaTissue annotations.
|
8
|
+
class AnnotationService < CaRuby::PersistenceService
|
9
|
+
# The IdGenerator calls the caTissue entity manager to create a new identifier for
|
10
|
+
# an annotation.
|
11
|
+
class IdGenerator
|
12
|
+
# Creates a new IdGenerator with the given caTissue entity_manager.
|
13
|
+
def initialize(entity_manager)
|
14
|
+
super()
|
15
|
+
@entity_manager = entity_manager
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a new identifier for the given annotation object.
|
19
|
+
def next_identifier(annotation)
|
20
|
+
logger.debug { "Generating identifier for annotation #{annotation}..." }
|
21
|
+
id = @entity_manager.get_next_identifier_for_entity(annotation.class.qp).to_i
|
22
|
+
# work-around caTissue bug - use a high number that won't conflict with the autogenerated range
|
23
|
+
# cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=404&sid=786325c9c46503529a2b64f0b66fed0a
|
24
|
+
if id.nil? or id.zero? then
|
25
|
+
id = Uniquifier.qualifier
|
26
|
+
logger.warn("Entity manager did not create annotation identifier; using id #{id}.")
|
27
|
+
end
|
28
|
+
id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates an AnnotationService for the given CaTissue::Database, service name and options.
|
33
|
+
#
|
34
|
+
# Supported options include the following:
|
35
|
+
# * :anchor - the required anchor class
|
36
|
+
# * :integration_service - the required IntegrationService
|
37
|
+
# * :entity_manager - the required EntityManager
|
38
|
+
# * +version+ - the optional caTissue version identifier
|
39
|
+
def initialize(database, service_name, options)
|
40
|
+
super(service_name, Options.get(:version, options))
|
41
|
+
@database = database
|
42
|
+
@anchor_class = Options.get(:anchor, options)
|
43
|
+
@integration_service = Options.get(:integration_service, options)
|
44
|
+
entity_manager = Options.get(:entity_manager, options)
|
45
|
+
@id_generator = IdGenerator.new(entity_manager)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Query the given domain object obj with the given attribute path. The query result is
|
49
|
+
# an array of CaRuby::Annotation objects. Overrides {CaRuby::PersistenceService#query} to
|
50
|
+
# delegate to the caTissue integration service when the penultimate search class is this
|
51
|
+
# AnnotationService anchor class, e.g.:
|
52
|
+
# query(CaTissue::SpecimenCollectionGroup.new(:name => 'Biobank_1_5'), :base_pathology_annotation)
|
53
|
+
# or
|
54
|
+
# query(CaTissue::ReceivedEventParameters.new(:user => user), :specimen_collection_group, :base_pathology_annotation)
|
55
|
+
def query(obj, *path)
|
56
|
+
# delegate path[0...-1] to database unless path.length < 2
|
57
|
+
return @database.query(obj, *path[0...-1]).map { |fetched| query(fetched, path[-1]) }.flatten unless path.length < 2
|
58
|
+
return super unless @anchor_class === obj.class
|
59
|
+
# fetch the anchor objects if necessary
|
60
|
+
return @database.query(obj).map { |fetched| query(fetched, *path) }.flatten if obj.identifier.nil?
|
61
|
+
# obj is an anchor with an identifier; make a template
|
62
|
+
template = obj.copy(:identifier)
|
63
|
+
# delegate to the integration service to find the anchor annotations
|
64
|
+
@integration_service.query(template, *path)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Augments the CaRuby::PersistenceService create method to handle caTissue annotation service peculiarities,
|
68
|
+
# e.g. assigning an identifier since assignment is not done automatically as is the case with the default
|
69
|
+
# application service.
|
70
|
+
def create(annotation)
|
71
|
+
annotation.identifier ||= @id_generator.next_identifier(annotation)
|
72
|
+
super
|
73
|
+
owner = annotation.owner
|
74
|
+
@integration_service.associate(owner, annotation) if CaRuby::Annotatable === owner
|
75
|
+
annotation
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'caruby/util/properties'
|
2
|
+
require 'catissue/database/annotation/integration_service'
|
3
|
+
require 'catissue/database/annotation/annotatable_service'
|
4
|
+
require 'catissue/database/annotation/annotation_service'
|
5
|
+
require 'catissue/database/annotation/entity_manager'
|
6
|
+
|
7
|
+
module CaTissue
|
8
|
+
module Annotation
|
9
|
+
# An Annotator creates annotation services for annotatable and annotation classes.
|
10
|
+
class Annotator
|
11
|
+
def initialize(database)
|
12
|
+
@database = database
|
13
|
+
#the sole DE integration service, used by the annotation services
|
14
|
+
@entity_manager = Annotation::EntityManager.instance
|
15
|
+
@integration_service = Annotation::IntegrationService.new(@entity_manager)
|
16
|
+
@anchor_svc_hash = {}
|
17
|
+
@ann_mod_svc_hash = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the CaRuby::PersistenceService for the given klass, or nil if klass is neither
|
21
|
+
# annotatable nor an annotation.
|
22
|
+
def service(klass)
|
23
|
+
annotatable_service(klass) or annotation_service(klass)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Returns the Annotation::AnnotatableService for the given klass, or nil if klass is not annotatable.
|
29
|
+
def annotatable_service(klass)
|
30
|
+
return @anchor_svc_hash[klass] ||= create_annotatable_service(klass) if JavaImport::AnnotatableClass === klass
|
31
|
+
annotatable_service(klass.superclass) if klass.superclass
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the Annotation::AnnotatorService for the given klass, or nil if klass is not an annotation.
|
35
|
+
def annotation_service(klass)
|
36
|
+
return @ann_mod_svc_hash[klass.annotation_module] ||= create_annotation_service(klass.annotation_module) if JavaImport::AnnotationClass === klass
|
37
|
+
annotation_service(klass.superclass) if klass.superclass
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_annotatable_service(klass)
|
41
|
+
Annotation::AnnotatableService.new(@database, @database.persistence_service, @integration_service)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_annotation_service(mod)
|
45
|
+
Annotation::AnnotationService.new(@database, mod.service, :anchor => mod.anchor_class,
|
46
|
+
:integration_service => @integration_service, :entity_manager => @entity_manager)
|
47
|
+
end
|
48
|
+
|
49
|
+
# TODO - delete if obsolete
|
50
|
+
# Returns the class => annotator hash from the given database's properties.
|
51
|
+
def create_service_hashes(database)
|
52
|
+
@anchor_svc_hash = {}
|
53
|
+
@pkg_svc_hash = {}
|
54
|
+
#the sole DE integration service, used by the annotation services
|
55
|
+
integration_service = Annotation::IntegrationService.new
|
56
|
+
entity_manager = Annotation::EntityManager.instance
|
57
|
+
# the anchor class => { package => { attribute => type } } hash
|
58
|
+
anchor_pkg_attrs_hash = CaTissue.access_properties[CaRuby::Domain::Properties::ANNOTATIONS_PROP]
|
59
|
+
return {} if anchor_pkg_attrs_hash.nil?
|
60
|
+
# the package => service name hash
|
61
|
+
pkg_svc_nm_hash = CaTissue.access_properties[CaRuby::Domain::Properties::ANN_SVCS_PROP]
|
62
|
+
if pkg_svc_nm_hash.nil? then
|
63
|
+
raise CaRuby::ConfigurationError.new("Annotation service property missing: #{CaRuby::Domain::Properties::ANN_SVCS_PROP}")
|
64
|
+
end
|
65
|
+
# build the anchor => service and package => service hashes
|
66
|
+
anchor_service = Annotation::AnnotatableService.new(database, database.persistence_service, integration_service)
|
67
|
+
# make an annotatable service for each anchor and an annotator service for each package
|
68
|
+
anchor_pkg_attrs_hash.each do |anchor_cls_nm, pkg_signatures_hash|
|
69
|
+
anchor_class = CaTissue.const_get(anchor_cls_nm)
|
70
|
+
@anchor_svc_hash[anchor_class] = anchor_service
|
71
|
+
pkg_signatures_hash.each do |pkg, signatures|
|
72
|
+
svc_nm = pkg_svc_nm_hash[pkg]
|
73
|
+
if svc_nm.nil? then
|
74
|
+
raise CaRuby::DatabaseError.new("Annotation service property value missing for package #{pkg} in property #{CaRuby::Domain::Properties::ANN_SVCS_PROP}")
|
75
|
+
end
|
76
|
+
@pkg_svc_hash[pkg] = Annotation::AnnotationService.new(database, svc_nm, :anchor => anchor_class,
|
77
|
+
:integration_service => integration_service, :entity_manager => entity_manager)
|
78
|
+
logger.debug { "Annotator service #{anchor_class.qp} #{svc_nm} created for attributes #{signatures.pp_s(:single_line)}" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'caruby/import/java'
|
2
|
+
|
3
|
+
module CaTissue
|
4
|
+
module Annotation
|
5
|
+
# EntityManager dependencies
|
6
|
+
java_import('edu.wustl.common.security.exceptions.UserNotAuthorizedException')
|
7
|
+
# EntityManager is the caTissue singleton Winnebago object for persisting annotations.
|
8
|
+
java_import('edu.common.dynamicextensions.entitymanager.EntityManager')
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'caruby/database/persistence_service'
|
2
|
+
|
3
|
+
module CaTissue
|
4
|
+
module Annotation
|
5
|
+
# An IntegrationService fetches and saves CaTissue anchor-annotation associations.
|
6
|
+
class IntegrationService < CaRuby::PersistenceService
|
7
|
+
SERVICE_NAME = 'deintegration'
|
8
|
+
|
9
|
+
java_import('deintegration.EntityMap')
|
10
|
+
java_import('deintegration.EntityMapRecord')
|
11
|
+
|
12
|
+
def initialize(entity_manager)
|
13
|
+
super(SERVICE_NAME)
|
14
|
+
@entity_manager = entity_manager
|
15
|
+
end
|
16
|
+
|
17
|
+
# Associates the given anchor domain object to annotation.
|
18
|
+
def associate(anchor, annotation)
|
19
|
+
logger.debug { "Associating annotation #{annotation} to owner #{anchor}..." }
|
20
|
+
association = create_entity_map_record(anchor, annotation)
|
21
|
+
create(association)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Removes the existing association between the given anchor domain object to annotation.
|
25
|
+
def dissociate(anchor, annotation)
|
26
|
+
association = create_entity_map_record(anchor, annotation)
|
27
|
+
delete(association)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Flag to work around caTissue and JRuby bugs
|
33
|
+
ENTITY_MAP_BUG = true
|
34
|
+
|
35
|
+
## The cruft below is adapted from caTissue ClientDemo_SCG.java and cleaned up (but still obscure). ##
|
36
|
+
|
37
|
+
def create_entity_map_record(anchor, annotation)
|
38
|
+
record = EntityMapRecord.new
|
39
|
+
raise CaRuby::DatabaseError.new("Annotation entity map static entity does not have an identifier: #{anchor}") if anchor.identifier.nil?
|
40
|
+
record.static_entity_record_id = anchor.identifier
|
41
|
+
raise CaRuby::DatabaseError.new("Annotation entity map dynamic entity does not have an identifier: #{annotation}") if annotation.identifier.nil?
|
42
|
+
record.dynamic_entity_record_id = annotation.identifier
|
43
|
+
record.form_context = form_context(anchor, annotation)
|
44
|
+
record.form_context_id = record.form_context.identifier if record.form_context
|
45
|
+
record
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the undocumented caTissue FormContext, which might be another bit of caTissue presentation
|
49
|
+
# flotsam polluting the data layer.
|
50
|
+
def form_context(anchor, annotation)
|
51
|
+
map = EntityMap.new
|
52
|
+
map.static_entity_id = anchor_entity_id(anchor)
|
53
|
+
return if ENTITY_MAP_BUG
|
54
|
+
# 2 bugs:
|
55
|
+
# * caTissue bug - bad container id query, cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=421&sid=5252d951301e598eebf3e90036da43cb
|
56
|
+
# * jRuby bug? - query on map call to map.pp_s wipes out the map java_class! Can't step into pp_s with debugger so can't isolate cause without more work
|
57
|
+
# TODO - isolate and file both bugs
|
58
|
+
#map.container_id = entity_container_id(annotation)
|
59
|
+
map.form_context_collection.first if query(map)
|
60
|
+
end
|
61
|
+
|
62
|
+
def entity_container_id(annotation)
|
63
|
+
entity_id = annotation_entity_id(annotation)
|
64
|
+
container_id = @entity_manager.get_container_id_for_entity(entity_id)
|
65
|
+
raise CaRuby::DatabaseError.new("Dynamic extension container id not found for annotation #{annotation}") if container_id.nil?
|
66
|
+
logger.debug { "Dynamic extension container id for #{annotation}: #{container_id}" } and container_id
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the undocumented caTissue entity id for the given anchor entity's Java class name.
|
70
|
+
def anchor_entity_id(anchor)
|
71
|
+
entity_id_for_class_designator(anchor.class.java_class.name)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the undocumented caTissue entity id for the given annotation entity's demodulized Java class name.
|
75
|
+
#
|
76
|
+
# caTissue alert - unlike #anchor_entity_id, {#annotation_entity_id} strips the leading package prefix from the annotation
|
77
|
+
# class name. caTissue DE API requires this undocumented inconsistency.
|
78
|
+
def annotation_entity_id(annotation)
|
79
|
+
entity_id_for_class_designator(annotation.class.java_class.name[/[^.]+$/])
|
80
|
+
end
|
81
|
+
|
82
|
+
def entity_id_for_class_designator(designator)
|
83
|
+
@entity_manager.get_entity_id(designator) or raise CaRuby::DatabaseError.new("Dynamic extension entity id not found for #{designator}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'caruby/util/log'
|
2
|
+
require 'catissue/database/controlled_values'
|
3
|
+
|
4
|
+
module CaTissue
|
5
|
+
# Finds attribute controlled values.
|
6
|
+
class ControlledValueFinder
|
7
|
+
# Creates a new ControlledValueFinder for the given attribute.
|
8
|
+
# The optional YAML properties file name maps input values to controlled values.
|
9
|
+
def initialize(attribute, file=nil)
|
10
|
+
@attribute = attribute
|
11
|
+
@remap_hash = load_controlled_value_hash(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the CV value for the given source value.
|
15
|
+
# If the value is remapped, then that value is returned.
|
16
|
+
# Otherwise, if the value is a standard CV, then the CV value is returned.
|
17
|
+
# Otherwise, a warning message is printed to the log and this method returns nil.
|
18
|
+
def controlled_value(value)
|
19
|
+
return if value.blank?
|
20
|
+
remapped = remapped_controlled_value(value)
|
21
|
+
return remapped if remapped
|
22
|
+
cv = supported_controlled_value(value)
|
23
|
+
logger.warn("#{@attribute} value '#{value}' ignored since it is not a recognized controlled value.") if cv.nil?
|
24
|
+
cv.value if cv
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def remapped_controlled_value(value)
|
30
|
+
@remap_hash[value]
|
31
|
+
end
|
32
|
+
|
33
|
+
def supported_controlled_value(value)
|
34
|
+
ControlledValues.instance.find(@attribute, value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_controlled_value_hash(file)
|
38
|
+
return {} unless file and File.exists?(file)
|
39
|
+
logger.debug { "Loading controlled value map for #{@attribute} from #{file}..." }
|
40
|
+
YAML::load_file(file)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'caruby/util/log'
|
3
|
+
require 'caruby/util/collection'
|
4
|
+
require 'caruby/util/options'
|
5
|
+
require 'caruby/util/visitor'
|
6
|
+
require 'catissue/resource'
|
7
|
+
require 'caruby/database/sql_executor'
|
8
|
+
require 'catissue/util/controlled_value'
|
9
|
+
require 'caruby/util/transitive_closure'
|
10
|
+
require 'caruby/domain/properties'
|
11
|
+
|
12
|
+
module CaTissue
|
13
|
+
# This ControlledValues class loads caTissue permissible values from the database.
|
14
|
+
# Use of this class requires the +dbi+ gem and the following caTissue database access
|
15
|
+
# properties are defined in the
|
16
|
+
# home directory .catissue.yaml file:
|
17
|
+
# * :database_host - the database host
|
18
|
+
# * :database - the database name
|
19
|
+
# * :database_user - the database username (not the caTissue login name)
|
20
|
+
# * :database_password - the database password (not the caTissue login password)
|
21
|
+
#
|
22
|
+
# The default :database_host is the application :host property value, which in turn
|
23
|
+
# defaults to +localhost+.
|
24
|
+
#
|
25
|
+
# The optional :database_port property overrides the default MySQL port.
|
26
|
+
#
|
27
|
+
# ControlledValues is an auxiliary utility class and is not used by the CaTissue Ruby API.
|
28
|
+
class ControlledValues
|
29
|
+
include Singleton
|
30
|
+
|
31
|
+
PUBLIC_ID_ROOTS_STMT = "select identifier, value from catissue_permissible_value where public_id = ? and parent_identifier is null or parent_identifier = 0"
|
32
|
+
|
33
|
+
CHILDREN_STMT = "select identifier, value from catissue_permissible_value where parent_identifier = ?"
|
34
|
+
|
35
|
+
INSERT_STMT = "insert into catissue_permissible_value (identifier, parent_identifier, public_id, value) values (?, ?, ?, ?)"
|
36
|
+
|
37
|
+
DELETE_STMT = "delete from catissue_permissible_value where identifier = ?"
|
38
|
+
|
39
|
+
MAX_ID_STMT = "select max(identifier) from catissue_permissible_value"
|
40
|
+
|
41
|
+
SEARCH_STMT = "select identifier from catissue_permissible_value where value collate latin1_bin = ?"
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
# the default database name, used for direct database access
|
45
|
+
CaTissue.access_properties[:database] ||= CaTissue::Database::DEF_DATABASE_NAME
|
46
|
+
@executor = CaRuby::SQLExecutor.new(CaTissue.access_properties)
|
47
|
+
@pid_loaded_hash = LazyHash.new { |pid| load_pid_cvs(pid) }
|
48
|
+
@pid_value_cv_hash = LazyHash.new do |pid|
|
49
|
+
CaseInsensitiveHash.new { |hash, value| hash[value] = load_cv(pid, value) unless value.nil? }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the transitive closure of each CV with the given public id and its children.
|
54
|
+
# The CVs are loaded from the database if necessary.
|
55
|
+
#
|
56
|
+
# The following public id aliases are supported:
|
57
|
+
# * :tissue_site
|
58
|
+
# * :clinical_diagnosis
|
59
|
+
#
|
60
|
+
#@param [String,Symbol] public_id_or_alias the caTissue public id or an alias defined above
|
61
|
+
# @return [<ControlledValue>] instances for the given public_id_or_alias
|
62
|
+
def for_public_id(public_id_or_alias)
|
63
|
+
pid = ControlledValue.standard_public_id(public_id_or_alias)
|
64
|
+
@pid_loaded_hash[pid].values
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the ControlledValue with the given public_id_or_alias and value.
|
68
|
+
# Loads the CV if necessary from the database. The loaded CV does not have a parent or children.
|
69
|
+
#
|
70
|
+
#@param [String,Symbol] public_id_or_alias the supported for_public_id alias
|
71
|
+
# @see #for_public_id load the CV hierarchy with supported aliases
|
72
|
+
def find(public_id_or_alias, value)
|
73
|
+
pid = ControlledValue.standard_public_id(public_id_or_alias)
|
74
|
+
@pid_value_cv_hash[pid][value]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a new controlled value record in the database from the given ControlledValue cv.
|
78
|
+
# The default identifier is the next identifier in the permissible values table.
|
79
|
+
#
|
80
|
+
# @param [ControlledValue] cv the controlled value to create
|
81
|
+
# @return cv
|
82
|
+
def create(cv)
|
83
|
+
cv.identifier ||= next_id
|
84
|
+
raise ArgumentError.new("Controlled value create is missing a public id") if cv.public_id.nil?
|
85
|
+
raise ArgumentError.new("Controlled value create is missing a value") if cv.value.nil?
|
86
|
+
logger.debug { "Creating controlled value #{cv} in the database..." }
|
87
|
+
@executor.execute { |dbh| dbh.prepare(INSERT_STMT).execute(cv.identifier, cv.parent_identifier, cv.public_id, cv.value) }
|
88
|
+
logger.debug { "Controlled value #{cv.public_id} #{cv.value} created with identifier #{cv.identifier}" }
|
89
|
+
@pid_value_cv_hash[cv.public_id][cv.value] = cv
|
90
|
+
end
|
91
|
+
|
92
|
+
# Deletes the given ControlledValue record in the database. Recursively deletes the
|
93
|
+
# transitive closure of children as well.
|
94
|
+
#
|
95
|
+
# @param [ControlledValue] cv the controlled value to delete
|
96
|
+
def delete(cv)
|
97
|
+
@executor.execute { |dbh| delete_recursive(cv, dbh.prepare(DELETE_STMT)) }
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def load_pid_cvs(pid)
|
103
|
+
fetch_cvs_with_public_id(pid, @pid_value_cv_hash[pid])
|
104
|
+
end
|
105
|
+
|
106
|
+
def load_cv(public_id, value)
|
107
|
+
logger.debug { "Loading controlled value #{public_id} #{value} from the database..." }
|
108
|
+
row = @executor.execute { |dbh| dbh.select_one(SEARCH_STMT, value) }
|
109
|
+
logger.debug { "Controlled value #{public_id} #{value} not found." } and return if row.nil?
|
110
|
+
identifier = row[0]
|
111
|
+
logger.debug { "Controlled value #{public_id} #{value} loaded with identifier #{identifier}." }
|
112
|
+
make_controlled_value(:identifier => identifier, :public_id => public_id, :value => value)
|
113
|
+
end
|
114
|
+
|
115
|
+
def next_id
|
116
|
+
@executor.execute { |dbh| dbh.select_one(MAX_ID_STMT)[0].to_i } + 1
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_recursive(cv, sth)
|
120
|
+
raise ArgumentError.new("Controlled value to delete is missing an identifier") if cv.identifier.nil?
|
121
|
+
logger.debug { "Deleting controlled value #{cv} from the database..." }
|
122
|
+
logger.debug { "Deleting controlled value #{cv} children: #{cv.children.pp_s}..." } unless cv.children.empty?
|
123
|
+
cv.children.each { |child| delete_recursive(child, sth) }
|
124
|
+
sth.execute(cv.identifier)
|
125
|
+
@pid_value_cv_hash[cv.public_id].delete(cv.value)
|
126
|
+
logger.debug { "Controlled value #{cv} deleted." }
|
127
|
+
end
|
128
|
+
|
129
|
+
def fetch_cvs_with_public_id(pid, value_cv_hash)
|
130
|
+
id_cv_hash = {}
|
131
|
+
logger.debug { "Loading #{pid} controlled values from the database..." }
|
132
|
+
cvs = []
|
133
|
+
@executor.execute do |dbh|
|
134
|
+
dbh.select_all(PUBLIC_ID_ROOTS_STMT, pid) do |row|
|
135
|
+
identifier, value = row
|
136
|
+
cvs << value_cv_hash[value] ||= make_controlled_value(:identifier => identifier, :public_id => pid, :value => value)
|
137
|
+
end
|
138
|
+
# load the root CVs children
|
139
|
+
cvs.each { |cv| fetch_descendants(cv, dbh, value_cv_hash) }
|
140
|
+
end
|
141
|
+
value_cv_hash
|
142
|
+
end
|
143
|
+
|
144
|
+
def fetch_descendants(cv, dbh, value_cv_hash)
|
145
|
+
children = []
|
146
|
+
dbh.select_all(CHILDREN_STMT, cv.identifier) do |row|
|
147
|
+
identifier, value = row
|
148
|
+
children << value_cv_hash[value] = make_controlled_value(:identifier => identifier, :public_id => pid, :parent => cv, :value => value)
|
149
|
+
end
|
150
|
+
# recurse to chidren
|
151
|
+
children.each { |cv| fetch_descendants(cv, dbh, value_cv_hash) }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a new ControlledValue with attributes set by the given attribute => value hash.
|
155
|
+
def make_controlled_value(value_hash)
|
156
|
+
cv = ControlledValue.new(value_hash[:value], value_hash[:parent])
|
157
|
+
cv.identifier = value_hash[:identifier]
|
158
|
+
cv.public_id = value_hash[:public_id]
|
159
|
+
cv
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|