ruby-atmos-pure 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,103 @@
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/atmos.rb', '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
+ desc 'install, require both gems'
82
+ task :install do
83
+ sh "sudo gem i pkg/#{rexml_spec.name}-#{rexml_spec.version}.gem"
84
+ sh "ruby test/require_test.rb"
85
+ sh "sudo gem i pkg/#{nokogiri_spec.name}-#{nokogiri_spec.version}.gem"
86
+ sh "ruby test/require_test.rb"
87
+ end
88
+
89
+ desc 'uninstall both gems'
90
+ task :uninstall do
91
+ sh "sudo gem uninstall #{nokogiri_spec.name} -x"
92
+ sh "sudo gem uninstall nokogiri -x"
93
+ sh "sudo gem uninstall #{rexml_spec.name} -x"
94
+ end
95
+
96
+ desc 'reinstall both gems'
97
+ task :reinstall => [:uninstall, :install]
98
+
99
+ desc 'push to rubygems.org'
100
+ task :push => [:install] do
101
+ sh "gem push pkg/#{rexml_spec.name}-#{rexml_spec.version}.gem"
102
+ sh "gem push pkg/#{nokogiri_spec.name}-#{nokogiri_spec.version}.gem"
103
+ end
data/lib/atmos.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'log4r'
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'time'
7
+ require 'base64'
8
+ require 'hmac-sha1'
9
+
10
+
11
+ Log4r::Logger.new('atmos')
12
+ Log4r::Logger.get('atmos').add(Log4r::StderrOutputter.new 'console')
13
+ Log4r::Logger.get('atmos').level = Log4r::FATAL
14
+ #Log4r::Logger.get('atmos').level = Log4r::WARN
15
+ #Log4r::Logger.get('atmos').level = Log4r::INFO
16
+ #Log4r::Logger.get('atmos').level = Log4r::DEBUG
17
+
18
+
19
+ $:.unshift(File.dirname(__FILE__))
20
+ require 'atmos/parser'
21
+ require 'atmos/version'
22
+ require 'atmos/util'
23
+ require 'atmos/exceptions'
24
+ require 'atmos/request'
25
+ require 'atmos/response'
26
+ require 'atmos/rest'
27
+ require 'atmos/store'
28
+ require 'atmos/attributes'
29
+ require 'atmos/object'
30
+
31
+ #
32
+ # This sets the default, but it can still be overridden by setting the parser elsewhere.
33
+ #
34
+ Atmos::Parser::parser = (File.exists?(File.join(File.dirname(__FILE__), 'atmos/parser/rexml.rb'))) ? Atmos::Parser::REXML : Atmos::Parser::NOKOGIRI
35
+ Atmos::LOG.warn("parser: #{Atmos::Parser::parser}")
@@ -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