ruby-atmos 0.6.0

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/COPYING ADDED
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2011 EMC Corporation
2
+ # All Rights Reserved
3
+ #
4
+ # This software contains the intellectual property of EMC Corporation
5
+ # or is licensed to EMC Corporation from third parties. Use of this
6
+ # software and the intellectual property contained therein is expressly
7
+ # limited to the terms and conditions of the License Agreement under which
8
+ # it is provided by or on behalf of EMC.
data/README ADDED
@@ -0,0 +1,129 @@
1
+ #
2
+ # = Atmos
3
+ #
4
+ # This gem is a Ruby library for EMC's Atmos (http://www.emc.com/atmos) REST API.
5
+ #
6
+ # Logging is done via the <tt>Log4r</tt> package. The name of the logger is the string +atmos+,
7
+ # and the log level is set to <tt>Log4r::FATAL</tt> by default.
8
+ #
9
+ # == Getting started
10
+ #
11
+ # You'll need this gem and a URL to your Atmos installation.
12
+ # You'll also need your Atmos authentication credentials.
13
+ #
14
+ # == Basics
15
+ # === XML Parser
16
+ #
17
+ # The very first thing is that you have to pick your XML parser. There are two options:
18
+ # <tt>nokogiri</tt> for performance or <tt>rexml</tt> for only pure ruby requirements.
19
+ # Setting the parser does the require, so make sure you have the gem you want to use installed.
20
+ # This gem doesn't require either, since it doesn't know which you plan to use.
21
+ #
22
+ # Atmos::Parser::parser = Atmos::Parser::NOKOGIRI
23
+ # Atmos::Parser::parser = Atmos::Parser::REXML
24
+ #
25
+ #
26
+ # === Datastore
27
+ #
28
+ # The Atmos installation you will be connecting to is your datastore. You
29
+ # can see more detailed information at <tt>Atmos::Store</tt>.
30
+ #
31
+ # store = Atmos::Store.new(:url => "https://mgmt.atmosonline.com",
32
+ # :uid => <your identifier>,
33
+ # :secret => <your secret>)
34
+ #
35
+ # From your store, you can create and retrieve objects. Note that objects
36
+ # are always retrieved without their data, so a progressive download can
37
+ # be done.
38
+ #
39
+ # obj = store.get(id)
40
+ # newobj = store.create(options)
41
+ #
42
+ # Now that you have a store, you can iterate through all listable tags, object ids
43
+ # by listable tag, and objects by listable tag. Remember that when an Atmos::Object
44
+ # is instantiated, it loads all ACLs and Metadata.
45
+ #
46
+ # store.each_listable_tag { |tag| }
47
+ #
48
+ # store.each_object_id_by_tag(tag) do |id|
49
+ # end
50
+ #
51
+ # store.each_object_by_tag(tag) do |obj|
52
+ # end
53
+ #
54
+ #
55
+ # === Objects
56
+ # See <tt>Atmos::Object</tt> for more detailed info on the object API.
57
+ #
58
+ # ==== Creating Objects
59
+ #
60
+ # At the very simplest, you can create an object with nothing. The mimetype
61
+ # will default to <tt>binary/octet-stream</tt>. The object will be created
62
+ # with no data, no metadata, with permissions set for only the creator to access.
63
+ #
64
+ # obj = store.create()
65
+ #
66
+ # You can also create an object with (any combination of) more information
67
+ # up front. (See <tt>Atmos::Store.create</tt> for more detailed info on
68
+ # object creation options.)
69
+ #
70
+ # obj = store.create(
71
+ # :user_acl => {'janet' => :full, 'xavier' => :read},
72
+ # :group_acl => {'other' => :read},
73
+ # :metadata => {'key' => 'value', 'another_key' => nil},
74
+ # :listable_metadata => {'lkey' => 'lvalue', 'another_lkey' => nil},
75
+ # :mimetype => "video/quicktime",
76
+ # :data => open("local/video_file.qt")
77
+ # )
78
+ #
79
+ # ===== Reading Objects
80
+ #
81
+ # You can get the data for an object all at once, which means it all
82
+ # gets read into memory all at once:
83
+ #
84
+ # @data = @obj.data
85
+ #
86
+ # Or progressively download it by passing a block to <tt>read</tt>:
87
+ #
88
+ # @file = open('mydata', 'w')
89
+ # @obj.read do |chunk|
90
+ # @file.write chunk
91
+ # end
92
+ #
93
+ #
94
+ # ===== Object Metadata
95
+ #
96
+ # An object has system and user metadata. User metadata can be modified,
97
+ # system metadata cannot. User metadata is further divided into listable
98
+ # and non-listable metadata. Listable metadata means the object is indexed
99
+ # on the key value of the metadata key/value pair (also known as a
100
+ # <tt>tag</tt>), and can be retrieved via the tag.
101
+ #
102
+ # Set or change user metadata, listable or non-listable, keys and values must be Strings:
103
+ #
104
+ # @obj.metadata[newkey] = "newvalue"
105
+ #
106
+ # (See <tt>Atmos::Metadata</tt> for more detailed info.)
107
+ #
108
+ #
109
+ # ===== Object ACL
110
+ #
111
+ # There are two ACLs on an object: <tt>user_acl</tt> and <tt>group_acl</tt>.
112
+ # User and group names are ruby Strings, while the values are one of the
113
+ # following symbols: <tt>:none</tt>, <tt>:read</tt>, <tt>:write</tt> ,<tt>:full</tt>.
114
+ # They are accessed as Hashes on the object. All normal Hash functions will work.
115
+ #
116
+ # When permissions are set to <tt>:none</tt> the pair disappears from the hash.
117
+ #
118
+ # @obj.user_acl['user'] = :full
119
+ # @obj.group_acl['other'] = :none
120
+ #
121
+ # (See <tt>Atmos::ACL</tt> for more detailed info.)
122
+ #
123
+ #
124
+ # ===== Other Object Functionality
125
+ #
126
+ # @obj.exists?
127
+ # @obj.delete
128
+ #
129
+
data/Rakefile ADDED
@@ -0,0 +1,94 @@
1
+ require 'rubygems'
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rdoc/task'
6
+ require 'rake/gempackagetask'
7
+ require 'test/unit'
8
+
9
+ require File.dirname(__FILE__) + '/lib/atmos'
10
+
11
+ task :default => :test
12
+
13
+
14
+ desc "run tests"
15
+ Rake::TestTask.new do |t|
16
+ libdir = File.expand_path('lib')
17
+ testdir = File.expand_path('test')
18
+
19
+ t.libs = [libdir, testdir]
20
+ #t.pattern = 'test/lib/test_*.rb'
21
+ t.verbose = true
22
+ #t.warning = true
23
+ end
24
+
25
+
26
+ Rake::RDocTask.new do |rdoc|
27
+ rdoc.rdoc_dir = 'doc'
28
+ rdoc.title = "EMC Atmos' REST API gem - ruby-atmos"
29
+ rdoc.options << '--line-numbers' << '--inline-source' << '--main=README'
30
+ rdoc.rdoc_files.include('README')
31
+ rdoc.rdoc_files.include('lib/*.rb')
32
+ rdoc.rdoc_files.include('lib/atmos/*.rb')
33
+ end
34
+
35
+
36
+ spec = Gem::Specification.new do |s|
37
+ s.platform = Gem::Platform::RUBY
38
+ s.name = 'ruby-atmos'
39
+ s.version = Gem::Version.new(Atmos::VERSION)
40
+ s.summary = "Client library for EMC's Atmos REST API"
41
+ s.description = "Ruby OO library to access an EMC Atmos server as a blobstore."
42
+ s.email = 'fleur.dragan@emc.com'
43
+ s.author = 'Fleur Dragan'
44
+ s.has_rdoc = true
45
+ s.extra_rdoc_files = %w(README COPYING)
46
+ s.homepage = 'http://www.emc.com/atmos'
47
+ #s.rubyforge_project = 'atmos'
48
+ s.files = FileList['Rakefile', 'lib/*/*.rb']
49
+ s.test_files = Dir['test/**/*']
50
+ s.require_path = 'lib'
51
+ s.add_dependency('log4r', '>=1.1.9')
52
+ s.add_dependency('ruby-hmac', '>=0.4.0')
53
+
54
+ s.rdoc_options = ['--title', "EMC Atmos' REST API gem - ruby-atmos",
55
+ '--main', 'README',
56
+ '--line-numbers', '--inline-source']
57
+ end
58
+
59
+ #
60
+ # DRY: set up gemspecs for both versions of gems
61
+ #
62
+ nokogiri_spec = spec.clone
63
+ nokogiri_spec.name = 'ruby-atmos'
64
+ nokogiri_spec.add_dependency('nokogiri', '>=1.4.4')
65
+ nokogiri_spec.description = "Ruby OO library to access an EMC Atmos server with nokogiri as XML parser."
66
+ nokogiri_spec.rubyforge_project = nokogiri_spec.name
67
+
68
+ rexml_spec = spec.clone
69
+ rexml_spec.name = 'ruby-atmos-pure'
70
+ rexml_spec.files += FileList['lib/atmos/*/rexml.rb']
71
+ rexml_spec.description = "Ruby OO library to access an EMC Atmos server with rexml as XML parser."
72
+ rexml_spec.rubyforge_project = rexml_spec.name
73
+
74
+
75
+ #
76
+ # creating the actual package tasks for the two gems
77
+ #
78
+ Rake::GemPackageTask.new(nokogiri_spec) { |pkg| }
79
+ Rake::GemPackageTask.new(rexml_spec) { |pkg| }
80
+
81
+
82
+ desc 'install gem with dependencies'
83
+ task :install => :package do
84
+ sh "sudo gem i pkg/#{spec.name}-#{spec.version}.gem"
85
+ end
86
+
87
+ desc 'uninstall gem'
88
+ task :uninstall do
89
+ sh "sudo gem uninstall #{spec.name} -x"
90
+ end
91
+
92
+ desc 'reinstall gem'
93
+ task :reinstall => [:uninstall, :install]
94
+
@@ -0,0 +1,545 @@
1
+ module Atmos
2
+
3
+ #
4
+ # == AttributeHashBase
5
+ #
6
+ # Base class for ACL and Metadata objects. Contains utility methods common to both,
7
+ # as well as implementations of standard ruby Hash functions to make both ACL and
8
+ # Metadata objects appear as hashes while interacting with Atmos at the same time.
9
+ #
10
+ #
11
+ class AttributeHashBase < Hash # :nodoc:
12
+ attr_reader :last_reload_at
13
+
14
+ @obj = nil
15
+ @header = nil
16
+ @last_reload_at = nil
17
+ @set_action = nil
18
+ @reload_action = nil
19
+ @delete_action = nil
20
+
21
+
22
+ def delete_with_atmos(key)
23
+ delete_without_atmos(key)
24
+ end
25
+ alias :delete_without_atmos :delete
26
+ alias :delete :delete_with_atmos
27
+
28
+
29
+ def clear_with_atmos
30
+ clear_without_atmos
31
+ end
32
+ alias :clear_without_atmos :clear
33
+ alias :clear :clear_with_atmos
34
+
35
+
36
+ def replace_with_atmos(h)
37
+ validate_input_hash(h)
38
+ replace_without_atmos(h)
39
+ end
40
+ alias :replace_without_atmos :replace
41
+ alias :replace :replace_with_atmos
42
+
43
+
44
+ def merge_with_atmos(h)
45
+ validate_input_hash(h)
46
+ merge_without_atmos(h)
47
+ end
48
+ alias :merge_without_atmos :merge
49
+ alias :merge :merge_with_atmos
50
+
51
+
52
+ def merge_with_atmos!(h)
53
+ validate_input_hash(h)
54
+
55
+ input = h.clone
56
+ input.each do |k,v|
57
+ input[k] = xlate_value_from_object_to_header(v)
58
+ end
59
+
60
+ response = @obj.request.do(@set_action, :id => @obj.aoid, @header => Atmos::Util.hash2header(input))
61
+ reload(@reload_action, @obj.aoid)
62
+ end
63
+ alias :merge_without_atmos! :merge!
64
+ alias :merge! :merge_with_atmos!
65
+ alias :update :merge!
66
+
67
+
68
+ def default_with_atmos=
69
+ raise Atmos::Exceptions::NotImplementedExceptions, "Not implemented for Atmos Object properties."
70
+ end
71
+ alias :default_without_atmos= :default=
72
+ alias :default= :default_with_atmos=
73
+
74
+
75
+ #
76
+ # Same as self[key] = value
77
+ #
78
+ def store(key, value)
79
+ self[key] = value
80
+ end
81
+
82
+
83
+ #
84
+ # This method is used internally to generate REST requests to the Atmos server.
85
+ #
86
+ # Each ACL or Metadata object knows what HTTP header it is representing
87
+ # in the Atmos REST API we're wrapping. This method returns that.
88
+ #
89
+ def header_name
90
+ return @header
91
+ end
92
+
93
+
94
+ #
95
+ # This method is used internally to generate REST requests to the Atmos server.
96
+ #
97
+ # Returns data as a String in the form of a valid HTTP header Atmos will
98
+ # recognize.
99
+ #
100
+ def header_value
101
+ Atmos::Util.hash2header(self)
102
+ end
103
+
104
+
105
+ #
106
+ # This method is used internally to generate REST requests to the Atmos server.
107
+ #
108
+ def to_header
109
+ "#{self.header_name}: #{self.header_value}"
110
+ end
111
+
112
+ def to_canonicalized_header
113
+ self.to_header.squeeze
114
+ end
115
+
116
+
117
+ protected
118
+ def validate_input_hash(h)
119
+ true
120
+ end
121
+
122
+ def xlate_value_from_header_to_object(value)
123
+ value
124
+ end
125
+
126
+ def reload(action, aoid, options = {})
127
+ response = @obj.request.do(action, options.merge({:id => aoid}))
128
+ Atmos::LOG.debug("AttributeHashBase.reload: type: #{@type}; header: #{@header}; value: #{response[@header]}")
129
+ newval = Atmos::Util.header2hash(response[@header])
130
+ Atmos::LOG.debug("AttributeHashBase.reload: newval: #{newval.inspect}")
131
+ if (!newval.nil?)
132
+ newval.each do |key,val|
133
+ newval[key] = xlate_value_from_header_to_object(val)
134
+ end
135
+ Atmos::LOG.debug("AttributeHashBase.reload: newval: #{newval.inspect}")
136
+ replace_without_atmos(newval)
137
+ end
138
+ Atmos::LOG.debug("AttributeHashBase.reload: self: #{self.inspect}")
139
+
140
+ @last_reload_at = Time.now()
141
+ end
142
+
143
+ end
144
+
145
+
146
+ # == Access Control Lists (ACLs)
147
+ #
148
+ # There are two hashes for access control, available as properties on the object: +user_acl+ and +group_acl+.
149
+ # The keys are the Atmos usernames and the values are one of :+none+, :+read+, :+write+, :+full+. The ACLs
150
+ # behave like normal Hash objects. All operations are executed against the Atmos server immediately.
151
+ #
152
+ # === Defaults
153
+ # By default, when you create an object, the user you gave as a parameter when instantiating Atmos::Store
154
+ # has full permissions on the object The default group is +other+. So:
155
+ #
156
+ # puts obj.user_acl.inspect => {user => :full}
157
+ # puts obj.group_acl.inspect => {other => :none}
158
+ #
159
+ #
160
+ # === Adding
161
+ # Adding permissions for a new user is as easy as adding another hash element:
162
+ #
163
+ # obj.user_acl[newuser] = :read
164
+ #
165
+ # puts obj.user_acl.inspect => {user => :full, newuser => :read}
166
+ #
167
+ #
168
+ # === Modifying
169
+ # User and group permissions can be modified by modifying the appropriate key value.
170
+ # Keep in mind that you CAN be dumb and give up access to your own objects,
171
+ # even if there is no other user that has access to them.
172
+ #
173
+ # obj.user_acl[newuser] = :full
174
+ # puts obj.user_acl.inspect => {user => :full, newuser => :full}
175
+ #
176
+ # obj.group_acl['other'] = :full
177
+ # puts obj.group_acl.inspect => {other => :full}
178
+ #
179
+ #
180
+ # === Deleting
181
+ # Remove any permissions for a given user or group, you can either modify existing permissions
182
+ # to :+none+, or you can delete the user/group name from the appropriate hash. When you do either,
183
+ # the name disappears entirely from the hash.
184
+ #
185
+ # obj.user_acl.delete(newuser)
186
+ # puts obj.user_acl.inspect => {user => :full}
187
+ #
188
+ # obj.user_acl[newuser] = :none
189
+ # puts obj.user_acl.inspect => {user => :full}
190
+ #
191
+ #
192
+ class ACL < AttributeHashBase
193
+ USER = 1
194
+ GROUP = 2
195
+
196
+ #
197
+ # This constructor is only meant for internal use. To get ACLs on an object:
198
+ #
199
+ # obj.user_acl => Hash
200
+ # obj.group_acl => Hash
201
+ #
202
+ def initialize(obj, type)
203
+ raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter cannot be nil." if (obj.nil?)
204
+ raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter must have an id." if (obj.aoid.nil?)
205
+ raise Atmos::Exceptions::ArgumentException, "The 'type' parameter must be Atmos::ACL::USER or Atmos::ACL::GROUP." if (![USER, GROUP].include?(type))
206
+
207
+ super()
208
+
209
+ @obj = obj
210
+ @type = type
211
+
212
+ @header = (@type == USER) ? 'x-emc-useracl' : 'x-emc-groupacl'
213
+ @delete_action = @set_action = (@type == USER) ? :set_user_acl : :set_group_acl
214
+ @reload_action = :list_acl
215
+
216
+ reload(@reload_action, @obj.aoid)
217
+ end
218
+
219
+
220
+ #
221
+ # Adds or modifies permissions for a user or group.
222
+ # The change is made on the Atmos server immediately.
223
+ # Valid values are :+none+, :+read+, :+write+, :+full+.
224
+ #
225
+ def []=(key,value)
226
+ validate_value(value)
227
+ response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{xlate_value_from_object_to_header(value)}")
228
+ reload(@reload_action, @obj.aoid)
229
+ end
230
+
231
+
232
+ #
233
+ # Returns +true+ if this ACL object is representing user ACLs.
234
+ #
235
+ def user?
236
+ @type == USER
237
+ end
238
+
239
+ #
240
+ # Returns +true+ if this ACL object is representing group ACLs.
241
+ #
242
+ def group?
243
+ @type == GROUP
244
+ end
245
+
246
+
247
+ #
248
+ # Removes permissions for specified user/group name. Update is made on the Atmos server immediately.
249
+ #
250
+ def delete(key)
251
+ response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{xlate_value_from_object_to_header(:none)}")
252
+ self.delete_without_atmos(key)
253
+ reload(@reload_action, @obj.aoid)
254
+ end
255
+
256
+ #
257
+ # Removes all permissions for all groups, or for all users except the one used to instantiate
258
+ # the Atmos::Store connection.
259
+ #
260
+ def clear
261
+ # do a reload to make absolutely sure ACL is up to date
262
+ reload(@reload_action, @obj.aoid)
263
+
264
+ values = {}
265
+ self.each do |k,v|
266
+ values[k] = xlate_value_from_object_to_header(:none)
267
+ end
268
+ values.delete(@obj.user)
269
+
270
+ response = @obj.request.do(@set_action, :id => @obj.aoid, @header => Atmos::Util.hash2header(values))
271
+ reload(@reload_action, @obj.aoid)
272
+ end
273
+
274
+
275
+ private
276
+ def validate_input_hash(h)
277
+ msg = nil
278
+ bad_keys = []
279
+ bad_values = []
280
+ good_values = [:none, :read, :write, :full]
281
+
282
+ h.each do |k,v|
283
+ bad_keys.push(k) if (k.nil? || !k.kind_of?(String))
284
+ bad_values.push(v) if (v.nil? || !good_values.include?(v))
285
+ end
286
+
287
+ msg = "The input has was bad: " if (!bad_keys.empty? || !bad_values.empty?)
288
+ msg += "bad keys: #{bad_keys.inspect} " if (!bad_keys.empty?)
289
+ msg += "bad values: #{bad_values.inspect}" if (!bad_values.empty?)
290
+
291
+ raise Atmos::Exceptions::ArgumentException, msg if (!msg.nil?)
292
+ end
293
+
294
+ def validate_value(value)
295
+ if (![:none, :read, :write, :full].include?(value))
296
+ raise Atmos::Exceptions::ArgumentException, "Valid permissions values are :none, :read, :write, :full"
297
+ end
298
+ end
299
+
300
+ def xlate_value_from_header_to_object(value)
301
+ case value
302
+ when 'NONE'
303
+ :none
304
+ when 'READ'
305
+ :read
306
+ when 'WRITE'
307
+ :write
308
+ when 'FULL_CONTROL'
309
+ :full
310
+ else
311
+ raise Atmos::Exceptions::InternalLibraryException, "Permissions type not recognized: #{value}"
312
+ end
313
+ end
314
+
315
+ def xlate_value_from_object_to_header(value)
316
+ case value
317
+ when :none
318
+ 'NONE'
319
+ when :read
320
+ 'READ'
321
+ when :write
322
+ 'WRITE'
323
+ when :full
324
+ 'FULL_CONTROL'
325
+ end
326
+ end
327
+ end
328
+
329
+
330
+ # == Metadata
331
+ # In all cases, +metadata+ refers to key/value pairs, and is represented
332
+ # as a ruby Hash attached to an Atmos::Object object.
333
+ #
334
+ # === Standard Metadata
335
+ # In Atmos, +metadata+ with no modifier (e.g. +listable+ or +system+)
336
+ # refers to metadata on an object that is not indexed. In other words,
337
+ # objects cannot be referenced by the metadata information. The only
338
+ # way to get this metadata information is to already have the object
339
+ # the metadata is attached to.
340
+ #
341
+ # ==== Defaults
342
+ # By default, when you create an object, there is no metadata. So:
343
+ #
344
+ # obj.metadata => {}
345
+ #
346
+ # ==== Adding
347
+ # Add metadata in key/value pairs as you would to a hash. Keys and
348
+ # values must be ruby Strings.
349
+ #
350
+ # obj.metadata[key] = value
351
+ # obj.metadata.store(key, value)
352
+ # obj.metadata.merge!({key => value})
353
+ #
354
+ # ==== Modifying
355
+ # Modify metadata as you would a hash. Keys and values must be ruby
356
+ # Strings.
357
+ #
358
+ # obj.metadata[pre-existing-key] = new-value
359
+ # obj.metadata.store(pre-existing-key, new-value)
360
+ # obj.metadata.merge!({pre-existing-key => new-value})
361
+ #
362
+ # ==== Deleting
363
+ # Delete metadata as you would a hash. Keys must be ruby
364
+ # Strings.
365
+ #
366
+ # obj.metadata.delete(key)
367
+ # obj.metadata.clear
368
+ #
369
+ # === Listable Metadata
370
+ # Listable metadata means atmos indexes the object by the key in the
371
+ # key/value metadata pair. The keys of listable metadata are also known
372
+ # as listable tags. Currently, an Atmos server can handle about 10k listable
373
+ # tags easily, so use judiciously with very large datasets.
374
+ #
375
+ # ==== Defaults
376
+ # By default, when you create an object, there is no listable metadata. So:
377
+ #
378
+ # obj.listable_metadata => {}
379
+ #
380
+ # ==== Adding
381
+ # Add metadata in key/value pairs as you would to a hash. Keys and
382
+ # values must be ruby Strings.
383
+ #
384
+ # obj.listable_metadata[key] = value
385
+ # obj.listable_metadata.store(key, value)
386
+ # obj.listable_metadata.merge!({key => value})
387
+ #
388
+ # ==== Modifying
389
+ # Modify metadata as you would a hash. Keys and values must be ruby
390
+ # Strings.
391
+ #
392
+ # obj.listable_metadata[pre-existing-key] = new-value
393
+ # obj.listable_metadata.store(pre-existing-key, new-value)
394
+ # obj.listable_metadata.merge!({pre-existing-key => new-value})
395
+ #
396
+ # ==== Deleting
397
+ # Delete metadata as you would a hash. Keys must be ruby
398
+ # Strings.
399
+ #
400
+ # obj.listable_metadata.delete(key)
401
+ # obj.listable_metadata.clear
402
+ #
403
+ # === System Metadata
404
+ # System metadata is a standard group of information maintained by Atmos for each object,
405
+ # such as +atime+, +mtime+, +type+, +policyname+.
406
+ #
407
+ # System metadata is not modifiable.
408
+ #
409
+ class Metadata < AttributeHashBase
410
+ LISTABLE = 1
411
+ NON_LISTABLE = 2
412
+ SYSTEM = 3
413
+
414
+
415
+ #
416
+ # This constructor is only meant for internal use. To get the metadata on an object:
417
+ #
418
+ # obj.metadata => Hash
419
+ # obj.listable_metadata => Hash
420
+ # obj.system_metadata => Hash
421
+ #
422
+ def initialize(obj, type)
423
+ raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter cannot be nil." if (obj.nil?)
424
+ raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter must have an id." if (obj.aoid.nil?)
425
+ raise Atmos::Exceptions::ArgumentException, "The 'type' parameter must be one of Atmos::Metadata::LISTABLE Atmos::Metadata::NON_LISTABLE Atmos::Metadata::SYSTEM." if (![SYSTEM, LISTABLE, NON_LISTABLE].include?(type))
426
+
427
+ super()
428
+
429
+ @obj = obj
430
+ @type = type
431
+
432
+ @header = (@type == LISTABLE) ? 'x-emc-listable-meta' : 'x-emc-meta'
433
+ @reload_action = (@type == SYSTEM) ? :list_system_metadata : :list_metadata
434
+ @set_action = (@type == LISTABLE) ? :set_listable_metadata : :set_metadata
435
+
436
+ reload(@reload_action, @obj.aoid)
437
+ end
438
+
439
+
440
+ #
441
+ # Adds the specified metadata to the object unless the object is representing system metadata.
442
+ #
443
+ # System metadata cannot be modified, so an Atmos::Exceptions::NotImplementedException
444
+ # is thrown.
445
+ #
446
+ # The change is made on the Atmos server immediately.
447
+ #
448
+ def []=(key,value)
449
+ raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
450
+ raise Atmos::Exceptions::ArgumentException, "The 'value' parameter must be of type String'." if (!value.nil? && !value.kind_of?(String))
451
+ response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{value}")
452
+ reload(@reload_action, @obj.aoid)
453
+ end
454
+
455
+
456
+ #
457
+ # Deletes all metadata from the object unless the object is representing system metadata.
458
+ #
459
+ # System metadata cannot be modified, so an Atmos::Exceptions::NotImplementedException
460
+ # is thrown.
461
+ #
462
+ # The change is made on the Atmos server immediately.
463
+ #
464
+ def clear
465
+ raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
466
+ reload(@reload_action, @obj.aoid)
467
+ response = @obj.request.do(:delete_metadata, :id => @obj.aoid, 'x-emc-tags' => self.keys.join(','))
468
+ self.clear_without_atmos
469
+ reload(@reload_action, @obj.aoid)
470
+ end
471
+
472
+
473
+ #
474
+ # Deletes the specified metadata from the object unless the
475
+ # object is representing system metadata. System metadata cannot
476
+ # be modified, so an <tt>Atmos::Exceptions::NotImplementedException</tt>
477
+ # is thrown.
478
+ #
479
+ # The deleted is executed on the Atmos server immediately.
480
+ #
481
+ # Required:
482
+ # *<tt>key</tt> -
483
+ def delete(key)
484
+ raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
485
+ response = @obj.request.do(:delete_metadata, :id => @obj.aoid, 'x-emc-tags' => key)
486
+ self.delete_without_atmos(key)
487
+ reload(@reload_action, @obj.aoid)
488
+ end
489
+
490
+
491
+ #
492
+ # Returns +true+ if this Metadata object is representing listable metadata.
493
+ #
494
+ def listable?
495
+ @type == LISTABLE
496
+ end
497
+
498
+
499
+ #
500
+ # Returns +true+ if this Metadata object is representing non-listable metadata.
501
+ #
502
+ def non_listable?
503
+ @type == NON_LISTABLE
504
+ end
505
+
506
+
507
+ #
508
+ # Returns +true+ if this Metadata object is representing system metadata.
509
+ #
510
+ def system?
511
+ @type == SYSTEM
512
+ end
513
+
514
+
515
+ private
516
+ #
517
+ # This method is defined to override AttributeHashBase.validate_input_hash,
518
+ # which simply returns true. This allows the base class to contain the
519
+ # method definitions for several standard Hash functions, yet allows the
520
+ # validation to be specialized for each subclass.
521
+ #
522
+ # Required:
523
+ # *<tt>h</tt> - the hash to validate
524
+ #
525
+ def validate_input_hash(h)
526
+ msg = nil
527
+ bad_keys = []
528
+ bad_values = []
529
+ good_values = [:none, :read, :write, :full]
530
+
531
+ h.each do |k,v|
532
+ bad_keys.push(k) if (k.nil? || !k.kind_of?(String))
533
+ bad_values.push(v) if (v.nil? || !good_values.include?(v))
534
+ end
535
+
536
+ msg = "The input has was bad: " if (!bad_keys.empty? || !bad_values.empty?)
537
+ msg += "bad keys: #{bad_keys.inspect} " if (!bad_keys.empty?)
538
+ msg += "bad values: #{bad_values.inspect}" if (!bad_values.empty?)
539
+
540
+ raise Atmos::Exceptions::ArgumentException, msg if (!msg.nil?)
541
+ end
542
+
543
+ end
544
+
545
+ end