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.
Files changed (133) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +44 -0
  5. data/bin/crtdump +31 -0
  6. data/bin/crtexample +18 -0
  7. data/bin/crtextract +47 -0
  8. data/bin/crtmigrate +17 -0
  9. data/bin/crtsmoke +27 -0
  10. data/examples/galena/README.md +53 -0
  11. data/examples/galena/bin/migrate.rb +42 -0
  12. data/examples/galena/bin/seed.rb +43 -0
  13. data/examples/galena/conf/extract/simple_fields.yaml +4 -0
  14. data/examples/galena/conf/migration/filter_fields.yaml +7 -0
  15. data/examples/galena/conf/migration/filter_migration.yaml +9 -0
  16. data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
  17. data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
  18. data/examples/galena/conf/migration/general_fields.yaml +42 -0
  19. data/examples/galena/conf/migration/general_migration.yaml +9 -0
  20. data/examples/galena/conf/migration/simple_fields.yaml +30 -0
  21. data/examples/galena/conf/migration/simple_migration.yaml +7 -0
  22. data/examples/galena/conf/migration/small_fields.yaml +24 -0
  23. data/examples/galena/conf/migration/small_migration.yaml +9 -0
  24. data/examples/galena/data/filter.csv +1 -0
  25. data/examples/galena/data/frozen.csv +1 -0
  26. data/examples/galena/data/general.csv +1 -0
  27. data/examples/galena/data/minimal.csv +1 -0
  28. data/examples/galena/data/simple.csv +1 -0
  29. data/examples/galena/data/small.csv +1 -0
  30. data/examples/galena/doc/CaTissue.html +93 -0
  31. data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
  32. data/examples/galena/doc/CaTissue/Participant.html +241 -0
  33. data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
  34. data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
  35. data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
  36. data/examples/galena/doc/Galena.html +290 -0
  37. data/examples/galena/doc/Galena/Seed.html +203 -0
  38. data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
  39. data/examples/galena/doc/_index.html +188 -0
  40. data/examples/galena/doc/class_list.html +36 -0
  41. data/examples/galena/doc/css/common.css +1 -0
  42. data/examples/galena/doc/css/full_list.css +53 -0
  43. data/examples/galena/doc/css/style.css +307 -0
  44. data/examples/galena/doc/file.README.html +108 -0
  45. data/examples/galena/doc/file_list.html +38 -0
  46. data/examples/galena/doc/frames.html +13 -0
  47. data/examples/galena/doc/index.html +108 -0
  48. data/examples/galena/doc/js/app.js +202 -0
  49. data/examples/galena/doc/js/full_list.js +149 -0
  50. data/examples/galena/doc/js/jquery.js +154 -0
  51. data/examples/galena/doc/method_list.html +179 -0
  52. data/examples/galena/doc/top-level-namespace.html +112 -0
  53. data/examples/galena/lib/README.html +33 -0
  54. data/examples/galena/lib/galena.rb +8 -0
  55. data/examples/galena/lib/galena/cli/seed.rb +43 -0
  56. data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
  57. data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
  58. data/examples/galena/lib/galena/seed/defaults.rb +97 -0
  59. data/lib/catissue.rb +26 -0
  60. data/lib/catissue/cli/command.rb +51 -0
  61. data/lib/catissue/cli/example.rb +31 -0
  62. data/lib/catissue/cli/migrate.rb +60 -0
  63. data/lib/catissue/cli/smoke.rb +45 -0
  64. data/lib/catissue/database.rb +451 -0
  65. data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
  66. data/lib/catissue/database/annotation/annotation_service.rb +79 -0
  67. data/lib/catissue/database/annotation/annotator.rb +84 -0
  68. data/lib/catissue/database/annotation/entity_manager.rb +10 -0
  69. data/lib/catissue/database/annotation/integration_service.rb +87 -0
  70. data/lib/catissue/database/controlled_value_finder.rb +43 -0
  71. data/lib/catissue/database/controlled_values.rb +162 -0
  72. data/lib/catissue/domain/abstract_domain_object.rb +8 -0
  73. data/lib/catissue/domain/abstract_position.rb +22 -0
  74. data/lib/catissue/domain/abstract_specimen.rb +288 -0
  75. data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
  76. data/lib/catissue/domain/address.rb +13 -0
  77. data/lib/catissue/domain/cancer_research_group.rb +11 -0
  78. data/lib/catissue/domain/capacity.rb +34 -0
  79. data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
  80. data/lib/catissue/domain/collection_event_parameters.rb +13 -0
  81. data/lib/catissue/domain/collection_protocol.rb +177 -0
  82. data/lib/catissue/domain/collection_protocol_event.rb +108 -0
  83. data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
  84. data/lib/catissue/domain/consent_tier_response.rb +13 -0
  85. data/lib/catissue/domain/consent_tier_status.rb +29 -0
  86. data/lib/catissue/domain/container.rb +234 -0
  87. data/lib/catissue/domain/container_position.rb +21 -0
  88. data/lib/catissue/domain/container_type.rb +131 -0
  89. data/lib/catissue/domain/department.rb +13 -0
  90. data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
  91. data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
  92. data/lib/catissue/domain/external_identifier.rb +22 -0
  93. data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
  94. data/lib/catissue/domain/institution.rb +13 -0
  95. data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
  96. data/lib/catissue/domain/order_details.rb +25 -0
  97. data/lib/catissue/domain/participant.rb +138 -0
  98. data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
  99. data/lib/catissue/domain/password.rb +11 -0
  100. data/lib/catissue/domain/race.rb +11 -0
  101. data/lib/catissue/domain/received_event_parameters.rb +25 -0
  102. data/lib/catissue/domain/scg_event_parameters.rb +11 -0
  103. data/lib/catissue/domain/site.rb +30 -0
  104. data/lib/catissue/domain/specimen.rb +456 -0
  105. data/lib/catissue/domain/specimen_array.rb +47 -0
  106. data/lib/catissue/domain/specimen_array_content.rb +19 -0
  107. data/lib/catissue/domain/specimen_array_type.rb +20 -0
  108. data/lib/catissue/domain/specimen_characteristics.rb +20 -0
  109. data/lib/catissue/domain/specimen_collection_group.rb +412 -0
  110. data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
  111. data/lib/catissue/domain/specimen_position.rb +38 -0
  112. data/lib/catissue/domain/specimen_protocol.rb +34 -0
  113. data/lib/catissue/domain/specimen_requirement.rb +143 -0
  114. data/lib/catissue/domain/storage_container.rb +204 -0
  115. data/lib/catissue/domain/storage_type.rb +82 -0
  116. data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
  117. data/lib/catissue/domain/user.rb +100 -0
  118. data/lib/catissue/extract/command.rb +31 -0
  119. data/lib/catissue/extract/delta.rb +62 -0
  120. data/lib/catissue/extract/extractor.rb +99 -0
  121. data/lib/catissue/migration/migrator.rb +101 -0
  122. data/lib/catissue/migration/shims.rb +108 -0
  123. data/lib/catissue/migration/uniquify.rb +111 -0
  124. data/lib/catissue/resource.rb +84 -0
  125. data/lib/catissue/util/controlled_value.rb +29 -0
  126. data/lib/catissue/util/location.rb +116 -0
  127. data/lib/catissue/util/log.rb +30 -0
  128. data/lib/catissue/util/person.rb +31 -0
  129. data/lib/catissue/util/position.rb +54 -0
  130. data/lib/catissue/util/storable.rb +34 -0
  131. data/lib/catissue/util/storage_type_holder.rb +30 -0
  132. data/lib/catissue/version.rb +7 -0
  133. metadata +212 -0
@@ -0,0 +1,111 @@
1
+ require 'caruby/util/validation'
2
+ require 'caruby/util/inflector'
3
+
4
+ module CaTissue
5
+ # import the Java class
6
+ java_import('edu.wustl.catissuecore.domain.SpecimenEventParameters')
7
+
8
+ class SpecimenEventParameters
9
+ include Resource
10
+
11
+ # date is a synonym for the more accurately titled timestamp attribute.
12
+ add_attribute_aliases(:date => :timestamp)
13
+
14
+ add_mandatory_attributes(:timestamp, :user)
15
+
16
+ # specimen is abstract but unfetched.
17
+ qualify_attribute(:specimen, :unfetched)
18
+
19
+ private
20
+
21
+ def self.allocate
22
+ raise NotImplementedError.new("SpecimenEventParameters is abstract; use the create method to make a new instance")
23
+ end
24
+
25
+ public
26
+
27
+ # Creates a SpecimenEventParameters of the specified subclass type. The type is a
28
+ # SpecimenEventParameters subclass name without the +EventParameters+ suffix, e.g.
29
+ # +Collection+. Lower-case, underscore symbols are supported and preferred, e.g. the
30
+ # :collection type creates a CollectionEventParameters.
31
+ #
32
+ # The required scg_or_specimen argument is either a SpecimenCollectionGroup or
33
+ # a Specimen.
34
+ #
35
+ # The optional params argument are attribute => value associations, e.g.
36
+ # SpecimenEventParameters.create_parameters(:collection, scg, :user => collector, :timestamp => DateTime.now)
37
+ def self.create_parameters(type, scg_or_specimen, params=Hash::EMPTY_HASH)
38
+ # make the class name by joining the camel-cased type prefix to the subclass suffix.
39
+ # classify converts a lower_case, underscore type to a valid class name, e.g. :check_in_check_out
40
+ # becomes CheckInCheckOut.
41
+ class_name = type.to_s.classify + SUBCLASS_SUFFIX
42
+ begin
43
+ klass = CaTissue.const_get(class_name.to_sym)
44
+ rescue
45
+ raise ArgumentError.new("Unsupported event parameters type: #{type}; #{class_name} must be a subtype of #{self}")
46
+ end
47
+ event_params = klass.new(params)
48
+ case scg_or_specimen
49
+ when SpecimenCollectionGroup then
50
+ event_params.specimen_collection_group = scg_or_specimen
51
+ when Specimen then
52
+ event_params.specimen = scg_or_specimen
53
+ when nil then
54
+ raise ArgumentError.new("Missing SpecimenEventParameters scg_or_specimen factory argument")
55
+ else
56
+ raise ArgumentError.new("Unsupported SpecimenEventParameters factory argument - expected SpecimenCollectionGroup or Specimen, found #{scg_or_specimen.class}")
57
+ end
58
+ event_params
59
+ end
60
+
61
+ # Returns the Specimen or SpecimenCollectionGroup to which this event is attached.
62
+ def subject
63
+ specimen.nil? ? specimen_collection_group : specimen
64
+ end
65
+
66
+ # Sets the scg_or_specimen subject to which this event is attached.
67
+ def subject=(scg_or_specimen)
68
+ spc_subject = scg_or_specimen if Specimen === scg_or_specimen
69
+ scg_subject = scg_or_specimen if SpecimenCollectionGroup === scg_or_specimen
70
+ specimen = spc_subject
71
+ specimen_collection_group = scg_subject
72
+ scg_or_specimen
73
+ end
74
+
75
+ def collection_protocol
76
+ specimen_collection_group.collection_protocol if specimen_collection_group
77
+ end
78
+
79
+ def validate
80
+ super
81
+ if subject.nil? then
82
+ raise ValidationError.new("Both specimen_collection_group and specimen are missing in SpecimenEventParameters #{self}")
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ SUBCLASS_SUFFIX = 'EventParameters'
89
+
90
+ # Sets each missing value to a default as follows:
91
+ # * default user is the SCG receiver
92
+ # * default timestamp is now
93
+ def add_defaults_local
94
+ super
95
+ self.timestamp ||= Java.now
96
+ self.user ||= default_user
97
+ end
98
+
99
+ # Returns whether the given value is either nil, empty or equals other.
100
+ def missing_or_match?(attribute, other)
101
+ value = send(attr)
102
+ value.nil_or_empty? or value == other.send(attr)
103
+ end
104
+
105
+ def default_user
106
+ scg = specimen_collection_group
107
+ scg ||= specimen.specimen_collection_group if specimen
108
+ scg.receiver if scg
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,38 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenPosition')
6
+
7
+ class SpecimenPosition
8
+ include Resource
9
+
10
+ add_mandatory_attributes(:storage_container)
11
+
12
+ add_attribute_aliases(:holder => :storage_container, :container => :storage_container, :occupant => :specimen)
13
+
14
+ # Each SpecimenPosition has a specimen and there is only one position per specimen.
15
+ set_secondary_key_attributes(:specimen)
16
+
17
+ set_attribute_inverse(:storage_container, :specimen_positions)
18
+
19
+ set_attribute_inverse(:specimen, :specimen_position)
20
+
21
+ qualify_attribute(:storage_container, :fetched)
22
+
23
+ # Returns a TransferEventParameters which serves as a proxy for saving this SpecimenPosition.
24
+ #
25
+ # caTissue alert - caTissue does not allow saving a SpecimenPosition directly in the database.
26
+ # Creating a TransferEventParameters sets the SpecimenPosition as a side-effect. Therefore,
27
+ # SpecimenPosition save is accomplished by creating a proxy TransferEventParameters instead.
28
+ def saver_proxy
29
+ xfr = CaTissue::TransferEventParameters.new(:specimen => specimen, :to => location)
30
+ if snapshot and changed? then
31
+ xfr.from_storage_container = snapshot[:storage_container]
32
+ xfr.from_position_dimension_one = snapshot[:position_dimension_one]
33
+ xfr.from_position_dimension_two = snapshot[:position_dimension_two]
34
+ end
35
+ xfr
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ require 'date'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenProtocol')
6
+
7
+ # The SpecimenProtocol domain class.
8
+ class SpecimenProtocol
9
+ include Resource
10
+
11
+ set_secondary_key_attributes(:short_title)
12
+
13
+ # caTissue alert - Bug #155: enrollment is incorrectly defined in SpecimenProtocol rather
14
+ # than CollectionProtocol. It is defaulted here until this defect is fixed.
15
+ add_attribute_defaults(:activity_status => 'Active', :enrollment => 0)
16
+
17
+ add_mandatory_attributes(:principal_investigator, :activity_status, :start_date, :title)
18
+
19
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #150:
20
+ # Create CollectionProtocol in API ignores startDate.
21
+ qualify_attribute(:start_date, :update_only)
22
+
23
+ private
24
+
25
+ # Sets the defaults if necessary. The start date is set to now. The title is
26
+ # set to the short title.
27
+ def add_defaults_local
28
+ super
29
+ self.title ||= short_title
30
+ self.short_title ||= title
31
+ self.start_date ||= Java::JavaUtil::Date.new
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,143 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.SpecimenRequirement')
4
+
5
+ # The SpecimenRequirement domain class.
6
+ class SpecimenRequirement
7
+ include Resource
8
+
9
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
10
+ # Initialize specimens if necessary.
11
+ #
12
+ # @return [Java::JavaUtil::Set] the specimens
13
+ def specimens
14
+ getSpecimenCollection or (self.specimens = Java::JavaUtil::LinkedHashSet.new)
15
+ end
16
+
17
+ add_attribute_aliases(:collection_event => :collection_protocol_event)
18
+
19
+ add_attribute_defaults(:initial_quantity => 0.0, :pathological_status => 'Not Specified', :specimen_type => 'Not Specified', :storage_type => 'Not Specified')
20
+
21
+ add_mandatory_attributes(:collection_protocol_event, :storage_type)
22
+
23
+ # SpecimenRequirement children are constrained to SpecimenRequirement.
24
+ set_attribute_type(:child_specimens, SpecimenRequirement)
25
+
26
+ # SpecimenRequirement parent is constrained to SpecimenRequirement.
27
+ set_attribute_type(:parent_specimen, SpecimenRequirement)
28
+
29
+ # As with Specimen, even though the inverse is declared in AbstractSpecimen, do so
30
+ # again here to ensure that there is no order dependency of the dependent declaration
31
+ # below on AbstractSpecimen metadata initialization.
32
+ set_attribute_inverse(:parent_specimen, :child_specimens)
33
+
34
+ # Unlike Specimen, a child SpecimenRequirement is not cascaded by caTissue.
35
+ # It is not auto-generated, i.e. it is not created from a template when the
36
+ # parent CPE is created.
37
+ add_dependent_attribute(:child_specimens, :logical)
38
+
39
+ # Overrides {Resource#owner} to return the parent_specimen, if it exists, or the collection_protocol_event otherwise.
40
+ def owner
41
+ parent_specimen or collection_protocol_event
42
+ end
43
+
44
+ # Returns the SpecimenRequirement in others which matches this SpecimenRequirement in the scope of an owner CollectionProtocolEvent.
45
+ # This method relaxes {CaRuby::Resource#match_in_owner_scope} for a SpecimenRequirement that matches any SpecimenRequirement
46
+ # in others with the same class, specimen type, pathological_status and characteristics.
47
+ def match_in_owner_scope(others)
48
+ others.detect do |other|
49
+ self.class === other and specimen_type == other.specimen_type and pathological_status == other.pathological_status and
50
+ characteristics and characteristics.match?(other.characteristics)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Returns whether this SpecimenRequirement characteristics matches the other SpecimenRequirement characteristics
57
+ # on the tissue site and tissue side.
58
+ def match_characteristics(other)
59
+ chr = characteristics
60
+ ochr = other.characteristics
61
+ chr and ochr and chr.tissue_side == ochr.tissue_side and chr.tissue_site == ochr.tissue_site
62
+ end
63
+
64
+ # Raises NotImplementedError, since SpecimenRequirement is abstract.
65
+ def self.allocate
66
+ raise NotImplementedError.new("SpecimenRequirement is abstract; use the create method to make a new instance")
67
+ end
68
+
69
+ public
70
+
71
+ def initialize(params=nil)
72
+ super
73
+ respond_to?(:specimens)
74
+ # work around caTissue Bug #64
75
+ self.specimens ||= Java::JavaUtil::LinkedHashSet.new
76
+ end
77
+
78
+ # Augments {CaRuby::Resource#validate} to verify that this SpecimenRequirement does not have multiple non-aliquot
79
+ # derivatives, which is disallowed by caTissue.
80
+ #
81
+ # caTissue alert - multiple SpecimenRequirement non-aliquot derivatives is accepted by caTissue but results
82
+ # in obscure downstream errors (cf. Bug #151).
83
+ #
84
+ # Raises ValidationError if this SpecimenRequirement has multiple non-aliquot derivatives.
85
+ def validate
86
+ super
87
+ if multiple_derivatives? then raise ValidationError.new("Multiple derivatives not supported by caTissue") end
88
+ end
89
+
90
+ # Creates a SpecimenRequirement of the given subclass type for the given CollectionProtocolEvent event.
91
+ # The type is a SpecimenRequirement subclass name without the +SpecimenRequirement+ suffix, e.g.
92
+ # +Tissue+. Lower-case, underscore symbols are supported and preferred, e.g. the
93
+ # :tissue type creates a TissueSpecimenRequirement.
94
+ #
95
+ # The optional params argument are attribute => value associations, e.g.
96
+ # SpecimenRequirement.create_requirement(:tissue, event, :specimen_type => 'RNA')
97
+ def self.create_requirement(type, event, params=Hash::EMPTY_HASH)
98
+ # make the class name by joining the camel-cased type prefix to the subclass suffix
99
+ class_name = type.to_s.classify + self.qp
100
+ begin
101
+ klass = CaTissue.const_get(class_name)
102
+ rescue
103
+ raise ArgumentError.new("Unsupported requirement type: #{type}; #{class_name} must be a subtype of #{self}")
104
+ end
105
+ klass.new(params.merge(:collection_protocol_event => event))
106
+ end
107
+
108
+ # Returns the {#collection_event} protocol, if any.
109
+ def collection_protocol
110
+ collection_event.protocol if collection_event
111
+ end
112
+
113
+ protected
114
+
115
+ # Returns the required attributes which are nil for this domain object.
116
+ # Overrides the CaRuby::Resource method to handle caTissue Bug #67 - SpecimenRequirement activityStatus cannot be set.
117
+ def missing_mandatory_attributes
118
+ invalid = super
119
+ # caTissue alert - Special case: work around caTissue Bug #67
120
+ if invalid.include?(:activity_status) then
121
+ invalid.delete(:activity_status)
122
+ end
123
+ # end of workaround
124
+ invalid
125
+ end
126
+
127
+ private
128
+
129
+ # Returns whether this SpecimenRequirement has multiple non-aliquot derivatives.
130
+ def multiple_derivatives?
131
+ children.size > 1 and children.any? { |drv| not drv.aliquot? }
132
+ end
133
+
134
+ # Adds the following default values, if necessary:
135
+ # * a generic SpecimenCharacteristics
136
+ # * the parent collection_event, if this SpecimenRequirement is derived
137
+ def add_defaults_local
138
+ super
139
+ self.collection_event ||= parent.collection_event if parent
140
+ self.specimen_characteristics ||= CaTissue::SpecimenCharacteristics.new
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,204 @@
1
+ require 'enumerator'
2
+ require 'caruby/util/partial_order'
3
+ require 'catissue/util/storage_type_holder'
4
+
5
+ module CaTissue
6
+ # import the Java class
7
+ java_import('edu.wustl.catissuecore.domain.StorageContainer')
8
+
9
+ # The +caTissue+ +StorageContainer+ domain class wrapper.
10
+ class StorageContainer
11
+ include StorageTypeHolder, Resource
12
+
13
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
14
+ # Initialize specimen_positions if necessary.
15
+ #
16
+ # @return [Java::JavaUtil::Set] the positions
17
+ def specimen_positions
18
+ getSpecimenPositionCollection or (self.specimen_positions = Java::JavaUtil::LinkedHashSet.new)
19
+ end
20
+
21
+ # Sets the storage type to the given value. Each empty holds collection is initialized
22
+ # from the corresponding StorageType holds collection.
23
+ def storage_type=(value)
24
+ if value and holds_storage_types and holds_storage_types.empty? then
25
+ holds_storage_types.merge!(value.holds_storage_types)
26
+ end
27
+ if value and holds_specimen_array_types and holds_specimen_array_types.empty? then
28
+ holds_specimen_array_types.merge!(value.holds_specimen_array_types)
29
+ end
30
+ if value and holds_specimen_classes and holds_specimen_classes.empty? then
31
+ holds_specimen_classes.merge!(value.holds_specimen_classes)
32
+ end
33
+ setStorageType(value)
34
+ end
35
+
36
+ def located_at_position=(value)
37
+ setLocatedAtPosition(value)
38
+ end
39
+
40
+ add_attribute_aliases(:container_type => :storage_type)
41
+
42
+ # Aternative to the inherited secondary key +name+.
43
+ set_alternate_key_attributes(:site, :barcode)
44
+
45
+ add_mandatory_attributes(:site, :storage_type)
46
+
47
+ qualify_attribute(:collection_protocols, :fetched)
48
+
49
+ set_attribute_type(:holds_specimen_array_types, CaTissue::SpecimenArrayType)
50
+
51
+ set_attribute_type(:holds_specimen_classes, String)
52
+
53
+ set_attribute_type(:holds_storage_types, CaTissue::StorageType)
54
+
55
+ def initialize(params=nil)
56
+ super(params)
57
+ # JRuby alert - specimen_positions is sometimes unrecognized unless primed with respond_to? call
58
+ respond_to?(:specimen_positions)
59
+ # work around caTissue Bug #64
60
+ self.specimen_positions ||= Java::JavaUtil::LinkedHashSet.new
61
+ end
62
+
63
+ # Corrects the +caTissue+ +occupied_positions+ method to include +specimen_positions+.
64
+ def all_occupied_positions
65
+ subcontainer_positions.union(specimen_positions)
66
+ end
67
+
68
+ alias :add_local :add
69
+
70
+ # Moves the given Storable from its current Position, if any, to this Container at the optional
71
+ # coordinate. The default coordinate is the first available slot within this Container.
72
+ # The storable Storable position is updated to reflect the new location. Returns self.
73
+ #
74
+ # If there is no coordinate and this container cannot hold the storable type, then the
75
+ # storable is added to a subcontainer which can hold the storable type.
76
+ #
77
+ # @param [Storable] the item to add
78
+ # @param [Coordinate] the storage location (default is first available location)
79
+ # @return [StorageContainer] self
80
+ # @raise [IndexError] if this Container is full
81
+ # @raise [IndexError] if the row and column are given but exceed the Container bounds
82
+ def add(storable, coordinate=nil)
83
+ return add_local(storable, coordinate) if coordinate
84
+ add_to_existing_container(storable) or add_to_new_subcontainer(storable) or out_of_bounds(storable)
85
+ self
86
+ end
87
+
88
+ alias :<< :add
89
+
90
+ # Finds the container with the given name, or creates a new container
91
+ # of the given type if necessary.
92
+ #
93
+ # @param [String] the container search name
94
+ # @param [CaTissue::StorageContainer] the container type
95
+ # @return a container with the given name
96
+ def find_subcontainer(name, type)
97
+ logger.debug { "Finding box with name #{name}..." }
98
+ ctr = CaTissue::StorageContainer.new(:name => name)
99
+ if ctr.find then
100
+ logger.debug { "Container found: #{ctr}." }
101
+ else
102
+ logger.debug { "Container not found: #{name}." }
103
+ create_subcontainer(name, type)
104
+ end
105
+ box
106
+ end
107
+
108
+ # @return a new Container with the given name and type in this Container
109
+ def create_subcontainer(name, type)
110
+ logger.debug { "Creating #{qp} subcontainer of type #{type} with name #{name}..." }
111
+ ctr = type.create_container(:name => name, :site => site)
112
+ self << ctr
113
+ ctr.create
114
+ logger.debug { "Made #{self} subcontainer #{ctr}." }
115
+ ctr
116
+ end
117
+
118
+ protected
119
+
120
+ # Returns the the content collection to which the storable is added, specimen_positions
121
+ # if storable is a Specimen, container_positions otherwise.
122
+ def content_collection_for(storable)
123
+ CaTissue::Specimen === storable ? specimen_positions : super
124
+ end
125
+
126
+ # Adds the given storable to a container within this StorageContainer's hierarchy.
127
+ #
128
+ # @param @storable (see #add)
129
+ # @return [StorageContainer, nil] self if added, nil otherwise
130
+ def add_to_existing_container(storable)
131
+ # the subcontainers in column, row sort order
132
+ scs = subcontainers.sort { |sc1, sc2| sc1.position.location <=> sc2.position.location }
133
+ # the first subcontainer that can hold the storable is preferred
134
+ if scs.detect { |ctr| ctr.add_to_existing_container(storable) if StorageContainer === ctr } then
135
+ self
136
+ elsif can_hold_child?(storable) then
137
+ add_local(storable)
138
+ end
139
+ end
140
+
141
+ # Creates a subcontainer which holds the given storable. Creates nested subcontainers as necessary.
142
+ #
143
+ # @param @storable (see #add)
144
+ # @return [StorageContainer, nil] self if a subcontainer was created, nil otherwise
145
+ def add_to_new_subcontainer(storable)
146
+ # the subcontainers in column, row sort order
147
+ scs = subcontainers.sort { |sc1, sc2| sc1.position.location <=> sc2.position.location }
148
+ # the first subcontainer that can hold the new subcontainer is preferred
149
+ if scs.detect { |ctr| ctr.add_to_new_subcontainer(storable) if StorageContainer === ctr } then
150
+ self
151
+ elsif not full? then
152
+ create_subcontainer_for(storable)
153
+ end
154
+ end
155
+
156
+ # @param [Storable] (see #add)
157
+ # @return whether this StorageContainer is not full and can hold the given item's StorableType
158
+ def can_hold_child?(storable)
159
+ st = storable.storable_type
160
+ not full? and child_types.any? { |ct| CaRuby::Resource.value_equal?(ct, st) }
161
+ end
162
+
163
+ private
164
+
165
+ # Adds the follwing defaults:
166
+ # * the default child_types are this container's CaTissue::ContainerType child_types.
167
+ # * the default site is the parent container site, if any.
168
+ def add_defaults_local
169
+ super
170
+ if child_types.empty? and container_type and not container_type.child_types.empty? then
171
+ container_type.child_types.each { |type| add_child_type(type) }
172
+ end
173
+ # Although this default is set by the caTissue app, it is good practice to do so here
174
+ # for clarity.
175
+ self.site ||= parent.site if parent
176
+ end
177
+
178
+ # @see #add_to_new_subcontainer
179
+ def create_subcontainer_for(storable)
180
+ # the StorageType path to storable
181
+ type_path = type_path_to(storable) || return
182
+ # create a container for each type leading to storable and add it to the parent container
183
+ ctr = type_path.reverse.inject(storable) do |occ, type|
184
+ subctr = type.create_container
185
+ subctr.site = site
186
+ logger.debug { "Created #{qp} #{subctr.container_type.name} subcontainer #{subctr} to hold #{occ}." }
187
+ subctr << occ
188
+ end
189
+ add_local(ctr)
190
+ end
191
+
192
+ # Returns a StorageType array from a child StorageType to a descendant StorageType which can
193
+ # hold the given storable, or nil if no such path exists.
194
+ def type_path_to(storable)
195
+ holds_storage_types.detect_value { |type| type.path_to(storable) }
196
+ end
197
+
198
+ private
199
+
200
+ def out_of_bounds(storable)
201
+ raise IndexError.new("Container #{name} does not have an available position for #{storable}")
202
+ end
203
+ end
204
+ end