active-fedora 9.1.2 → 9.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/History.txt +90 -0
  5. data/active-fedora.gemspec +2 -2
  6. data/lib/active_fedora.rb +17 -3
  7. data/lib/active_fedora/associations.rb +77 -0
  8. data/lib/active_fedora/associations/association.rb +2 -2
  9. data/lib/active_fedora/associations/basic_contains_association.rb +52 -0
  10. data/lib/active_fedora/associations/builder/directly_contains.rb +23 -0
  11. data/lib/active_fedora/associations/builder/directly_contains_one.rb +44 -0
  12. data/lib/active_fedora/associations/builder/has_and_belongs_to_many.rb +2 -23
  13. data/lib/active_fedora/associations/builder/indirectly_contains.rb +26 -0
  14. data/lib/active_fedora/associations/builder/property.rb +10 -0
  15. data/lib/active_fedora/associations/collection_association.rb +42 -45
  16. data/lib/active_fedora/associations/collection_proxy.rb +6 -0
  17. data/lib/active_fedora/associations/contained_finder.rb +41 -0
  18. data/lib/active_fedora/associations/container_proxy.rb +9 -0
  19. data/lib/active_fedora/associations/contains_association.rb +20 -38
  20. data/lib/active_fedora/associations/delete_proxy.rb +28 -0
  21. data/lib/active_fedora/associations/directly_contains_association.rb +56 -0
  22. data/lib/active_fedora/associations/directly_contains_one_association.rb +113 -0
  23. data/lib/active_fedora/associations/has_and_belongs_to_many_association.rb +6 -14
  24. data/lib/active_fedora/associations/has_many_association.rb +0 -3
  25. data/lib/active_fedora/associations/id_composite.rb +30 -0
  26. data/lib/active_fedora/associations/indirectly_contains_association.rb +90 -0
  27. data/lib/active_fedora/associations/rdf.rb +8 -4
  28. data/lib/active_fedora/associations/record_composite.rb +39 -0
  29. data/lib/active_fedora/attached_files.rb +1 -1
  30. data/lib/active_fedora/attributes.rb +28 -10
  31. data/lib/active_fedora/attributes/active_triple_attribute.rb +17 -0
  32. data/lib/active_fedora/attributes/om_attribute.rb +29 -0
  33. data/lib/active_fedora/attributes/rdf_datastream_attribute.rb +47 -0
  34. data/lib/active_fedora/attributes/stream_attribute.rb +46 -0
  35. data/lib/active_fedora/autosave_association.rb +2 -2
  36. data/lib/active_fedora/base.rb +3 -0
  37. data/lib/active_fedora/containers/container.rb +38 -0
  38. data/lib/active_fedora/containers/direct_container.rb +5 -0
  39. data/lib/active_fedora/containers/indirect_container.rb +7 -0
  40. data/lib/active_fedora/core.rb +4 -48
  41. data/lib/active_fedora/delegated_attribute.rb +5 -98
  42. data/lib/active_fedora/fedora.rb +1 -1
  43. data/lib/active_fedora/fedora_attributes.rb +4 -26
  44. data/lib/active_fedora/file.rb +87 -159
  45. data/lib/active_fedora/file/attributes.rb +63 -0
  46. data/lib/active_fedora/file/streaming.rb +46 -0
  47. data/lib/active_fedora/file_relation.rb +7 -0
  48. data/lib/active_fedora/identifiable.rb +87 -0
  49. data/lib/active_fedora/inbound_relation_connection.rb +21 -0
  50. data/lib/active_fedora/indexers.rb +10 -0
  51. data/lib/active_fedora/indexers/global_indexer.rb +30 -0
  52. data/lib/active_fedora/indexers/null_indexer.rb +12 -0
  53. data/lib/active_fedora/indexing/map.rb +4 -5
  54. data/lib/active_fedora/indexing_service.rb +3 -22
  55. data/lib/active_fedora/loadable_from_json.rb +8 -0
  56. data/lib/active_fedora/pathing.rb +24 -0
  57. data/lib/active_fedora/persistence.rb +15 -8
  58. data/lib/active_fedora/rdf.rb +2 -0
  59. data/lib/active_fedora/rdf/fcrepo4.rb +1 -0
  60. data/lib/active_fedora/rdf/field_map.rb +90 -0
  61. data/lib/active_fedora/rdf/field_map_entry.rb +28 -0
  62. data/lib/active_fedora/rdf/indexing_service.rb +9 -23
  63. data/lib/active_fedora/rdf/rdf_datastream.rb +1 -2
  64. data/lib/active_fedora/reflection.rb +16 -15
  65. data/lib/active_fedora/relation/delegation.rb +15 -4
  66. data/lib/active_fedora/relation/finder_methods.rb +4 -4
  67. data/lib/active_fedora/schema.rb +26 -0
  68. data/lib/active_fedora/schema_indexing_strategy.rb +25 -0
  69. data/lib/active_fedora/simple_datastream.rb +2 -2
  70. data/lib/active_fedora/solr_query_builder.rb +3 -2
  71. data/lib/active_fedora/version.rb +1 -1
  72. data/lib/active_fedora/with_metadata.rb +1 -1
  73. data/spec/integration/associations/rdf_spec.rb +49 -0
  74. data/spec/integration/base_spec.rb +19 -0
  75. data/spec/integration/belongs_to_association_spec.rb +6 -6
  76. data/spec/integration/collection_association_spec.rb +4 -4
  77. data/spec/integration/complex_rdf_datastream_spec.rb +12 -12
  78. data/spec/integration/datastream_rdf_nested_attributes_spec.rb +1 -1
  79. data/spec/integration/direct_container_spec.rb +254 -0
  80. data/spec/integration/directly_contains_one_association_spec.rb +102 -0
  81. data/spec/integration/file_spec.rb +16 -5
  82. data/spec/integration/has_many_associations_spec.rb +93 -58
  83. data/spec/integration/indirect_container_spec.rb +251 -0
  84. data/spec/integration/rdf_nested_attributes_spec.rb +1 -1
  85. data/spec/integration/relation_spec.rb +43 -27
  86. data/spec/spec_helper.rb +1 -1
  87. data/spec/unit/attributes_spec.rb +6 -6
  88. data/spec/unit/collection_proxy_spec.rb +28 -0
  89. data/spec/unit/file_spec.rb +1 -1
  90. data/spec/unit/files_hash_spec.rb +4 -4
  91. data/spec/unit/has_and_belongs_to_many_association_spec.rb +11 -9
  92. data/spec/unit/indexers/global_indexer_spec.rb +41 -0
  93. data/spec/unit/indexing_service_spec.rb +0 -21
  94. data/spec/unit/loadable_from_json_spec.rb +31 -0
  95. data/spec/unit/pathing_spec.rb +37 -0
  96. data/spec/unit/rdf/indexing_service_spec.rb +3 -3
  97. data/spec/unit/rdf_resource_datastream_spec.rb +26 -7
  98. data/spec/unit/schema_indexing_strategy_spec.rb +68 -0
  99. data/spec/unit/solr_query_builder_spec.rb +1 -1
  100. data/spec/unit/solr_service_spec.rb +1 -1
  101. metadata +49 -8
@@ -0,0 +1,113 @@
1
+ module ActiveFedora
2
+ module Associations
3
+ # Filters a DirectContainer relationship, returning the first item that matches the given :type
4
+ class DirectlyContainsOneAssociation < SingularAssociation #:nodoc:
5
+
6
+ # Finds objects contained by the container predicate (either the configured has_member_relation or ldp:contains)
7
+ # TODO: Refactor this to use solr (for efficiency) instead of parsing the RDF graph. Requires indexing ActiveFedora::File objects into solr, including their RDF.type and, if possible, the id of their container
8
+ def find_target
9
+ # filtered_objects = container_association_proxy.to_a.select { |o| o.metadata_node.type.include?(options[:type]) }
10
+ query_node = if container_predicate = options[:has_member_relation]
11
+ owner
12
+ else
13
+ container_predicate = ::RDF::Vocab::LDP.contains
14
+ container_association.container # Use the :through association's container
15
+ end
16
+
17
+ contained_uris = query_node.resource.query(predicate: container_predicate).map { |r| r.object.to_s }
18
+ contained_uris.each do |object_uri|
19
+ contained_object = klass.find(klass.uri_to_id(object_uri))
20
+ return contained_object if get_type_from_record(contained_object).include?(options[:type])
21
+ end
22
+ return nil # if nothing was found & returned while iterating on contained_uris, return nil
23
+ end
24
+
25
+ # Adds record to the DirectContainer identified by the container_association
26
+ # Relies on container_association.initialize_attributes to appropriately set things like record.uri
27
+ def add_to_container(record)
28
+ container_association.add_to_target(record) # adds record to corresponding Container
29
+ # TODO is send necessary?
30
+ container_association.send(:initialize_attributes, record) # Uses the :through association initialize the record with things like the correct URI for a direclty contained object
31
+ end
32
+
33
+ # Replaces association +target+ with +record+
34
+ # Ensures that this association's +type+ is set on the record and adds the record to the association's DirectContainer
35
+ def replace(record, *)
36
+ if record
37
+ raise_on_type_mismatch(record)
38
+ remove_existing_target
39
+ add_type_to_record(record, options[:type])
40
+ add_to_container(record)
41
+ else
42
+ remove_existing_target
43
+ end
44
+
45
+ self.target = record
46
+ end
47
+
48
+ def updated?
49
+ @updated
50
+ end
51
+
52
+ private
53
+
54
+ def remove_existing_target
55
+ @target ||= find_target
56
+ if @target
57
+ container_association_proxy.delete @target
58
+ @updated = true
59
+ end
60
+ end
61
+
62
+ # Overrides initialize_attributes to ensure that record is initialized with attributes from the corresponding container
63
+ def initialize_attributes(record)
64
+ super
65
+ container_association.initialize_attributes(record)
66
+ end
67
+
68
+ # Returns the Reflection corresponding to the direct container association that's being filtered
69
+ def container_reflection
70
+ @container_reflection ||= @owner.class.reflect_on_association(@reflection.options[:through])
71
+ end
72
+
73
+ # Returns the DirectContainerAssociation corresponding to the direct container that's being filtered
74
+ def container_association
75
+ container_association_proxy.proxy_association
76
+ end
77
+
78
+ # Returns the ContainerAssociationProxy corresponding to the direct container that's being filtered
79
+ def container_association_proxy
80
+ @owner.send(@reflection.options[:through])
81
+ end
82
+
83
+ # Adds type_uri to the RDF.type assertions on record
84
+ def add_type_to_record(record, type_uri)
85
+ metadata_node = metadata_node_for_record(record)
86
+ types = metadata_node.type
87
+ unless types.include?(type_uri)
88
+ types << type_uri
89
+ metadata_node.set_value(:type, types)
90
+ end
91
+ return record
92
+ end
93
+
94
+ # Returns the RDF.type assertions for the record
95
+ def get_type_from_record(record)
96
+ metadata_node_for_record(record).type
97
+ end
98
+
99
+ # Returns the RDF node that contains metadata like RDF.type assertions for the record
100
+ # Sometimes this is the record, other times it's record.metadata_node
101
+ def metadata_node_for_record(record)
102
+ if record.respond_to?(:type) && record.respond_to?(:set_value)
103
+ return record
104
+ elsif record.respond_to?(:metadata_node)
105
+ return record.metadata_node
106
+ else
107
+ raise ArgumentError, "record must either have a metadata node or must respond to .type"
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -46,20 +46,6 @@ module ActiveFedora
46
46
  result && records
47
47
  end
48
48
 
49
-
50
- def find_target
51
- page_size = @reflection.options[:solr_page_size]
52
- page_size ||= 200
53
- ids = owner[reflection.foreign_key]
54
- return [] if ids.blank?
55
- solr_result = []
56
- 0.step(ids.size,page_size) do |startIdx|
57
- query = ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids.slice(startIdx,page_size))
58
- solr_result += ActiveFedora::SolrService.query(query, rows: page_size)
59
- end
60
- return ActiveFedora::QueryResultBuilder.reify_solr_results(solr_result)
61
- end
62
-
63
49
  # In a HABTM, just look in the RDF, no need to run a count query from solr.
64
50
  def count(options = {})
65
51
  owner[reflection.foreign_key].size
@@ -96,6 +82,12 @@ module ActiveFedora
96
82
  owner[reflection.foreign_key]
97
83
  end
98
84
 
85
+ def find_target
86
+ ids = owner[reflection.foreign_key]
87
+ return [] if ids.blank?
88
+ ActiveFedora::Base.find(ids)
89
+ end
90
+
99
91
  end
100
92
  end
101
93
  end
@@ -35,19 +35,16 @@ module ActiveFedora
35
35
  record[inverse.foreign_key] = owner.id
36
36
  else # HABTM
37
37
  record[inverse.foreign_key] ||= []
38
- #TODO use primary_key instead of `owner.id`
39
38
  record[inverse.foreign_key] += [owner.id]
40
39
  end
41
40
  elsif owner.persisted?
42
41
  inverse = reflection.inverse_of
43
42
  if inverse && inverse.collection?
44
43
  record[inverse.foreign_key] ||= []
45
- #TODO use primary_key instead of `owner.id`
46
44
  record[inverse.foreign_key] += [owner.id]
47
45
  elsif inverse && inverse.klass == ActiveFedora::Base
48
46
  record[inverse.foreign_key] = owner.id
49
47
  else
50
- #TODO use primary_key instead of `owner.id`
51
48
  record[reflection.foreign_key] = owner.id
52
49
  end
53
50
  end
@@ -0,0 +1,30 @@
1
+ module ActiveFedora::Associations
2
+ ##
3
+ # A composite object for an array of IDs. This abstracts away the fact that an
4
+ # ID might be either a relative ID or a URI to a resource.
5
+ class IDComposite
6
+ attr_reader :ids, :id_translator
7
+ include Enumerable
8
+ def initialize(ids, id_translator)
9
+ @ids = ids
10
+ @id_translator = id_translator
11
+ end
12
+
13
+ # @return [Array<relative_id>]
14
+ def each
15
+ ids.each do |id|
16
+ yield convert(id)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def convert(id)
23
+ if id.start_with?("http")
24
+ id_translator.call(id)
25
+ else
26
+ id
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ module ActiveFedora
2
+ module Associations
3
+ class IndirectlyContainsAssociation < ContainsAssociation #:nodoc:
4
+
5
+ # TODO we may want to split this into two subclasses, one for has_member_relation
6
+ # and the other for is_member_of_relation
7
+
8
+ def insert_record(record, force = true, validate = true)
9
+ container.save!
10
+ if force
11
+ record.save!
12
+ else
13
+ return false unless record.save(validate: validate)
14
+ end
15
+
16
+ save_through_record(record)
17
+
18
+ return true
19
+ end
20
+
21
+ def find_target
22
+ if container_predicate = options[:has_member_relation]
23
+ uris = owner.resource.query(predicate: container_predicate).map { |r| r.object.to_s }
24
+ uris.map { |object_uri| klass.find(klass.uri_to_id(object_uri)) }
25
+ else # is_member_of_relation
26
+ # TODO this is a lot of reads. Avoid this path
27
+ container_predicate = ::RDF::Vocab::LDP.contains
28
+ proxy_uris = container.resource.query(predicate: container_predicate).map { |r| r.object.to_s }
29
+ proxy_uris.map { |uri| proxy_class.find(proxy_class.uri_to_id(uri))[options[:foreign_key]] }
30
+ end
31
+
32
+
33
+ end
34
+
35
+ def container
36
+ @container ||= begin
37
+ IndirectContainer.find_or_initialize(ActiveFedora::Base.uri_to_id(uri)).tap do |container|
38
+ container.parent = @owner
39
+ container.has_member_relation = Array(options[:has_member_relation])
40
+ container.is_member_of_relation = Array(options[:is_member_of_relation])
41
+ container.inserted_content_relation = Array(options[:inserted_content_relation])
42
+ end
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def initialize_attributes(record) #:nodoc:
49
+ #record.uri = ActiveFedora::Base.id_to_uri(container.mint_id)
50
+ # set_inverse_instance(record)
51
+ end
52
+
53
+ private
54
+
55
+ def delete_records(records, method)
56
+ container.reload # Reload container to get updated LDP.contains
57
+ records.each do |record|
58
+ delete_record(record)
59
+ end
60
+ end
61
+
62
+ def delete_record(record)
63
+ record_proxy_finder.find(record).delete
64
+ end
65
+
66
+ def record_proxy_finder
67
+ ContainedFinder.new(container: container, repository: composite_proxy_repository)
68
+ end
69
+
70
+ def composite_proxy_repository
71
+ RecordComposite::Repository.new(base_repository: proxy_class)
72
+ end
73
+
74
+ def save_through_record(record)
75
+ build_proxy_node({}) do |node|
76
+ node[options[:foreign_key]] = record
77
+ node.save
78
+ end
79
+ end
80
+
81
+ def build_proxy_node(attributes, &block)
82
+ proxy_class.new({ id: container.mint_id }.merge(attributes), &block)
83
+ end
84
+
85
+ def proxy_class
86
+ @proxy_class ||= options[:through].constantize
87
+ end
88
+ end
89
+ end
90
+ end
@@ -33,9 +33,15 @@ module ActiveFedora
33
33
  end
34
34
  end
35
35
 
36
- # TODO Detect when this is the only relationship for this predicate, then skip the filtering.
37
36
  def filtering_required?
38
- reflection.klass != ActiveFedora::Base
37
+ return false if reflection.klass == ActiveFedora::Base
38
+ reflections_with_same_predicate.count > 1
39
+ end
40
+
41
+ # Count the number of reflections that have the same predicate as the reflection
42
+ # for this association.
43
+ def reflections_with_same_predicate
44
+ owner.class.outgoing_reflections.select { |k, v| v.options[:predicate] == reflection.predicate }
39
45
  end
40
46
 
41
47
  # @return [Array<RDF::URI>]
@@ -48,8 +54,6 @@ module ActiveFedora
48
54
  owner.resource.query(subject: owner.rdf_subject, predicate: reflection.predicate).enum_statement
49
55
  end
50
56
 
51
-
52
- # TODO this is a huge waste of time that can be completely avoided if the attributes aren't sharing predicates.
53
57
  # @return [Array<RDF::URI>]
54
58
  def filter_by_class(candidate_uris)
55
59
  return [] if candidate_uris.empty?
@@ -0,0 +1,39 @@
1
+ module ActiveFedora::Associations
2
+ ##
3
+ # A Composite for records - currently only supports delete interface.
4
+ # The goal is to push commands down to the containing records.
5
+ class RecordComposite
6
+ attr_reader :records
7
+ include Enumerable
8
+ def initialize(records:)
9
+ @records = records
10
+ end
11
+
12
+ def each
13
+ records.each do |record|
14
+ yield record
15
+ end
16
+ end
17
+
18
+ def delete
19
+ self.each(&:delete)
20
+ end
21
+ ##
22
+ # A Repository which returns a composite from #find instead of a single
23
+ # record. Delegates find to a base repository.
24
+ class Repository
25
+ attr_reader :base_repository
26
+ delegate :translate_uri_to_id, to: :base_repository
27
+ def initialize(base_repository:)
28
+ @base_repository = base_repository
29
+ end
30
+
31
+ def find(ids)
32
+ records = ids.map do |id|
33
+ base_repository.find(id)
34
+ end
35
+ RecordComposite.new(records: records)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -124,7 +124,7 @@ module ActiveFedora
124
124
  private
125
125
  def create_singleton_association(file_path)
126
126
  self.undeclared_files << file_path.to_sym
127
- association = Associations::ContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, {class_name: 'ActiveFedora::File'}, self.class))
127
+ association = Associations::BasicContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, {class_name: 'ActiveFedora::File'}, self.class))
128
128
  @association_cache[file_path.to_sym] = association
129
129
 
130
130
  self.singleton_class.send :define_method, accessor_name(file_path) do
@@ -177,7 +177,7 @@ module ActiveFedora
177
177
  elsif properties.key?(:delegate_to)
178
178
  define_delegated_accessor([name], properties.delete(:delegate_to), properties.reverse_merge(multiple: true), &block)
179
179
  else
180
- raise "You must provide `:datastream' or `:predicate' options to property"
180
+ raise "You must provide `:delegate_to' or `:predicate' options to property"
181
181
  end
182
182
  end
183
183
 
@@ -186,7 +186,7 @@ module ActiveFedora
186
186
  def define_active_triple_accessor(name, properties, &block)
187
187
  warn_duplicate_predicates name, properties
188
188
  properties = { multiple: true }.merge(properties)
189
- find_or_create_defined_attribute(name, nil, properties)
189
+ find_or_create_defined_attribute(name, ActiveTripleAttribute, properties)
190
190
  raise ArgumentError, "#{name} is a keyword and not an acceptable property name." if protected_property_name? name
191
191
  reflection = ActiveFedora::Attributes::PropertyBuilder.build(self, name, properties, &block)
192
192
  ActiveTriples::Reflection.add_reflection self, name, reflection
@@ -197,6 +197,10 @@ module ActiveFedora
197
197
  def define_delegated_accessor(fields, delegate_target, options, &block)
198
198
  define_attribute_methods fields
199
199
  fields.each do |f|
200
+ klass = datastream_class_for_name(delegate_target)
201
+ attribute_properties = options.merge(delegate_target: delegate_target, klass: klass)
202
+ find_or_create_defined_attribute f, attribute_class(klass), attribute_properties
203
+
200
204
  create_attribute_reader(f, delegate_target, options)
201
205
  create_attribute_setter(f, delegate_target, options)
202
206
  add_attribute_indexing_config(f, &block) if block_given?
@@ -204,8 +208,7 @@ module ActiveFedora
204
208
  end
205
209
 
206
210
  def add_attribute_indexing_config(name, &block)
207
- # TODO the hash can be initalized to return on of these
208
- index_config[name] ||= ActiveFedora::Indexing::Map::IndexObject.new &block
211
+ index_config[name] ||= ActiveFedora::Indexing::Map::IndexObject.new(name, &block)
209
212
  end
210
213
 
211
214
  def warn_duplicate_predicates new_name, new_properties
@@ -215,31 +218,46 @@ module ActiveFedora
215
218
  end
216
219
  end
217
220
 
218
- def find_or_create_defined_attribute(field, dsid, args)
219
- delegated_attributes[field] ||= DelegatedAttribute.new(field, dsid, datastream_class_for_name(dsid), args)
221
+ # @param [Symbol] field the field to find or create
222
+ # @param [Class] klass the class to use to delegate the attribute (e.g.
223
+ # ActiveTripleAttribute, OmAttribute, or RdfDatastreamAttribute)
224
+ # @param [Hash] args
225
+ # @option args [String] :delegate_target the path to the delegate
226
+ # @option args [Class] :klass the class to create
227
+ # @option args [true,false] :multiple (false) true for multi-value fields
228
+ # @option args [Array<Symbol>] :at path to a deep node
229
+ # @return [DelegatedAttribute] the found or created attribute
230
+ def find_or_create_defined_attribute(field, klass, args)
231
+ delegated_attributes[field] ||= klass.new(field, args)
220
232
  end
221
233
 
222
234
  # @param [String] dsid the datastream id
223
235
  # @return [Class] the class of the datastream
224
236
  def datastream_class_for_name(dsid)
225
- reflection = reflect_on_association(dsid)
237
+ reflection = reflect_on_association(dsid.to_sym)
226
238
  reflection ? reflection.klass : ActiveFedora::File
227
239
  end
228
240
 
229
241
  def create_attribute_reader(field, dsid, args)
230
- find_or_create_defined_attribute(field, dsid, args)
231
-
232
242
  define_method field do |*opts|
233
243
  array_reader(field, *opts)
234
244
  end
235
245
  end
236
246
 
237
247
  def create_attribute_setter(field, dsid, args)
238
- find_or_create_defined_attribute(field, dsid, args)
239
248
  define_method "#{field}=".to_sym do |v|
240
249
  self[field]=v
241
250
  end
242
251
  end
252
+
253
+ def attribute_class(klass)
254
+ if klass < ActiveFedora::RDFDatastream
255
+ RdfDatastreamAttribute
256
+ else
257
+ OmAttribute
258
+ end
259
+ end
260
+
243
261
  end
244
262
  end
245
263
  end