waz-storage 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +9 -9
  2. data/CHANGELOG.rdoc +72 -72
  3. data/Gemfile +4 -4
  4. data/Gemfile.lock +46 -40
  5. data/LICENSE +18 -18
  6. data/README.rdoc +310 -310
  7. data/lib/waz-blobs.rb +4 -4
  8. data/lib/waz-queues.rb +6 -6
  9. data/lib/waz-storage.rb +39 -39
  10. data/lib/waz-tables.rb +4 -4
  11. data/lib/waz/blobs/blob_object.rb +122 -122
  12. data/lib/waz/blobs/container.rb +172 -161
  13. data/lib/waz/blobs/exceptions.rb +10 -10
  14. data/lib/waz/blobs/service.rb +181 -156
  15. data/lib/waz/queues/exceptions.rb +28 -28
  16. data/lib/waz/queues/message.rb +64 -64
  17. data/lib/waz/queues/queue.rb +164 -164
  18. data/lib/waz/queues/service.rb +105 -105
  19. data/lib/waz/storage/base.rb +70 -70
  20. data/lib/waz/storage/exceptions.rb +33 -33
  21. data/lib/waz/storage/validation_rules.rb +25 -25
  22. data/lib/waz/tables/edm_type_helper.rb +44 -44
  23. data/lib/waz/tables/exceptions.rb +44 -44
  24. data/lib/waz/tables/service.rb +178 -178
  25. data/lib/waz/tables/table.rb +74 -74
  26. data/lib/waz/tables/table_array.rb +10 -10
  27. data/rakefile +8 -21
  28. data/{tests → spec}/configuration.rb +22 -22
  29. data/{tests/waz/blobs/blob_object_test.rb → spec/waz/blobs/blob_object_spec.rb} +80 -80
  30. data/{tests/waz/blobs/container_test.rb → spec/waz/blobs/container_spec.rb} +175 -162
  31. data/{tests/waz/blobs/service_test.rb → spec/waz/blobs/service_spec.rb} +336 -282
  32. data/{tests/waz/queues/message_test.rb → spec/waz/queues/message_spec.rb} +32 -32
  33. data/{tests/waz/queues/queue_test.rb → spec/waz/queues/queue_spec.rb} +205 -205
  34. data/{tests/waz/queues/service_test.rb → spec/waz/queues/service_spec.rb} +298 -298
  35. data/{tests → spec}/waz/storage/base_tests.rb +81 -81
  36. data/{tests/waz/storage/shared_key_core_service_test.rb → spec/waz/storage/shared_key_core_service_spec.rb} +141 -141
  37. data/{tests/waz/tables/service_test.rb → spec/waz/tables/service_spec.rb} +613 -613
  38. data/{tests/waz/tables/table_test.rb → spec/waz/tables/table_spec.rb} +97 -97
  39. data/waz-storage.gemspec +29 -27
  40. metadata +47 -26
@@ -1,70 +1,70 @@
1
- module WAZ
2
- module Storage
3
- # This class is used to handle a general connection with Windows Azure Storage Account and it
4
- # should be used at least once on the application bootstrap or configuration file.
5
- #
6
- # The usage is pretty simple as it's depicted on the following sample
7
- # WAZ::Storage::establish_connection!(:account_name => "my_account_name",
8
- # :access_key => "your_base64_key",
9
- # :use_ssl => false)
10
- #
11
- class Base
12
- class << self
13
- # Sets the basic configuration parameters to use the API on the current context
14
- # required parameters are :account_name, :access_key.
15
- #
16
- # All other parameters are optional.
17
- def establish_connection!(options = {})
18
- raise InvalidOption, :account_name unless options.keys.include? :account_name
19
- raise InvalidOption, :access_key if !options.keys.include? :use_sas_auth_only unless options.keys.include? :access_key
20
- raise InvalidOption, :use_sas_auth_only if !options.keys.include? :access_key unless options.keys.include? :use_sas_auth_only
21
- raise InvalidOption, :sharedaccesssignature if !options.keys.include? :access_key unless options.keys.include? :sharedaccesssignature and options.keys.include? :use_sas_auth_only
22
- options[:use_ssl] = false unless options.keys.include? :use_ssl
23
- (@connections ||= []) << options
24
- end
25
-
26
- # Block Syntax
27
- #
28
- # Pushes the named repository onto the context-stack,
29
- # yields a new session, and pops the context-stack.
30
- #
31
- # This helps you set contextual operations like in the following sample
32
- #
33
- # Base.establish_connection(options) do
34
- # # do some operations on the given context
35
- # end
36
- #
37
- # The context is restored to the previous one (what you configured on establish_connection!)
38
- # or nil.
39
- #
40
- # Non-Block Syntax
41
- #
42
- # Behaves exactly as establish_connection!
43
- def establish_connection(options = {}) # :yields: current_context
44
- establish_connection!(options)
45
- if (block_given?)
46
- begin
47
- return yield
48
- ensure
49
- @connections.pop() if connected?
50
- end
51
- end
52
- end
53
-
54
- # Returns the default connection (last set) parameters. It will raise an exception WAZ::Storage::NotConnected
55
- # when there's no connection information registered.
56
- def default_connection
57
- raise NotConnected unless connected?
58
- return @connections.last
59
- end
60
-
61
- # Returns a value indicating whether the current connection information has been set or not.
62
- def connected?
63
- return false if (@connections.nil?)
64
- return false if (@connections.empty?)
65
- return true
66
- end
67
- end
68
- end
69
- end
70
- end
1
+ module WAZ
2
+ module Storage
3
+ # This class is used to handle a general connection with Windows Azure Storage Account and it
4
+ # should be used at least once on the application bootstrap or configuration file.
5
+ #
6
+ # The usage is pretty simple as it's depicted on the following sample
7
+ # WAZ::Storage::establish_connection!(:account_name => "my_account_name",
8
+ # :access_key => "your_base64_key",
9
+ # :use_ssl => false)
10
+ #
11
+ class Base
12
+ class << self
13
+ # Sets the basic configuration parameters to use the API on the current context
14
+ # required parameters are :account_name, :access_key.
15
+ #
16
+ # All other parameters are optional.
17
+ def establish_connection!(options = {})
18
+ raise InvalidOption, :account_name unless options.keys.include? :account_name
19
+ raise InvalidOption, :access_key if !options.keys.include? :use_sas_auth_only unless options.keys.include? :access_key
20
+ raise InvalidOption, :use_sas_auth_only if !options.keys.include? :access_key unless options.keys.include? :use_sas_auth_only
21
+ raise InvalidOption, :sharedaccesssignature if !options.keys.include? :access_key unless options.keys.include? :sharedaccesssignature and options.keys.include? :use_sas_auth_only
22
+ options[:use_ssl] = false unless options.keys.include? :use_ssl
23
+ (@connections ||= []) << options
24
+ end
25
+
26
+ # Block Syntax
27
+ #
28
+ # Pushes the named repository onto the context-stack,
29
+ # yields a new session, and pops the context-stack.
30
+ #
31
+ # This helps you set contextual operations like in the following sample
32
+ #
33
+ # Base.establish_connection(options) do
34
+ # # do some operations on the given context
35
+ # end
36
+ #
37
+ # The context is restored to the previous one (what you configured on establish_connection!)
38
+ # or nil.
39
+ #
40
+ # Non-Block Syntax
41
+ #
42
+ # Behaves exactly as establish_connection!
43
+ def establish_connection(options = {}) # :yields: current_context
44
+ establish_connection!(options)
45
+ if (block_given?)
46
+ begin
47
+ return yield
48
+ ensure
49
+ @connections.pop() if connected?
50
+ end
51
+ end
52
+ end
53
+
54
+ # Returns the default connection (last set) parameters. It will raise an exception WAZ::Storage::NotConnected
55
+ # when there's no connection information registered.
56
+ def default_connection
57
+ raise NotConnected unless connected?
58
+ return @connections.last
59
+ end
60
+
61
+ # Returns a value indicating whether the current connection information has been set or not.
62
+ def connected?
63
+ return false if (@connections.nil?)
64
+ return false if (@connections.empty?)
65
+ return true
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,33 +1,33 @@
1
- module WAZ
2
- module Storage
3
- # This class is the base exception from where all the exceptions raised from this API
4
- # inherit from. If you want to handle an exception that your code may throw and you don't
5
- # know which specific type you should handle, handle this type of exception.
6
- class StorageException < StandardError
7
- end
8
-
9
- # This exception raises whenever a required parameter for initializing any class isn't provided. From
10
- # WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
11
- class InvalidOption < StorageException
12
- def initialize(missing_option)
13
- super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
14
- end
15
- end
16
-
17
- # This exception is raised when the user tries to perform an operation on any Storage API class
18
- # without previously specificying the connection options.
19
- class NotConnected < StorageException
20
- def initialize
21
- super("You should establish connection before using the services, the connection configuration is required.")
22
- end
23
- end
24
-
25
- # This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
26
- # if values on the blocklisttype aren't [all|uncommitted|committed]
27
- class InvalidParameterValue < StorageException
28
- def initialize(args = {})
29
- super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
30
- end
31
- end
32
- end
33
- end
1
+ module WAZ
2
+ module Storage
3
+ # This class is the base exception from where all the exceptions raised from this API
4
+ # inherit from. If you want to handle an exception that your code may throw and you don't
5
+ # know which specific type you should handle, handle this type of exception.
6
+ class StorageException < StandardError
7
+ end
8
+
9
+ # This exception raises whenever a required parameter for initializing any class isn't provided. From
10
+ # WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
11
+ class InvalidOption < StorageException
12
+ def initialize(missing_option)
13
+ super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
14
+ end
15
+ end
16
+
17
+ # This exception is raised when the user tries to perform an operation on any Storage API class
18
+ # without previously specificying the connection options.
19
+ class NotConnected < StorageException
20
+ def initialize
21
+ super("You should establish connection before using the services, the connection configuration is required.")
22
+ end
23
+ end
24
+
25
+ # This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
26
+ # if values on the blocklisttype aren't [all|uncommitted|committed]
27
+ class InvalidParameterValue < StorageException
28
+ def initialize(args = {})
29
+ super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,26 +1,26 @@
1
- module WAZ
2
- module Storage
3
- class ValidationRules
4
- class << self
5
- # Validates that the Container/Queue name given matches with the requirements of Windows Azure.
6
- #
7
- # -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
8
- # -Every dash (-) character must be immediately preceded and followed by a letter or number.
9
- # -All letters in a container name must be lowercase.
10
- # -Container/Queue names must be from 3 through 63 characters long.
11
- def valid_name?(name)
12
- name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
13
- end
14
-
15
- # Validates that the Table name given matches with the requirements of Windows Azure.
16
- #
17
- # -Table names must start with at least one lower / upper character.
18
- # -Table names can have character or any digit starting from the second position.
19
- # -Table names must be from 3 through 63 characters long.
20
- def valid_table_name?(name)
21
- name =~ /^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$/
22
- end
23
- end
24
- end
25
- end
1
+ module WAZ
2
+ module Storage
3
+ class ValidationRules
4
+ class << self
5
+ # Validates that the Container/Queue name given matches with the requirements of Windows Azure.
6
+ #
7
+ # -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
8
+ # -Every dash (-) character must be immediately preceded and followed by a letter or number.
9
+ # -All letters in a container name must be lowercase.
10
+ # -Container/Queue names must be from 3 through 63 characters long.
11
+ def valid_name?(name)
12
+ name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
13
+ end
14
+
15
+ # Validates that the Table name given matches with the requirements of Windows Azure.
16
+ #
17
+ # -Table names must start with at least one lower / upper character.
18
+ # -Table names can have character or any digit starting from the second position.
19
+ # -Table names must be from 3 through 63 characters long.
20
+ def valid_table_name?(name)
21
+ name =~ /^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$/
22
+ end
23
+ end
24
+ end
25
+ end
26
26
  end
@@ -1,45 +1,45 @@
1
- module WAZ
2
- module Tables
3
- class EdmTypeHelper
4
- class << self
5
- def parse_from(item)
6
- return nil if !item.attributes['m:null'].nil? and item.attributes['m:null'] == 'true'
7
- case item.attributes['m:type']
8
- when 'Edm.Int16', 'Edm.Int32', 'Edm.Int64'
9
- item.text.to_i
10
- when 'Edm.Single', 'Edm.Double'
11
- item.text.to_f
12
- when 'Edm.Boolean'
13
- item.text == 'true'
14
- when 'Edm.DateTime'
15
- Time.parse(item.text)
16
- when 'Edm.Binary'
17
- StringIO.new(Base64.decode64(item.text))
18
- else
19
- item.text
20
- end
21
- end
22
-
23
- def parse_to(item)
24
- case item.class.name
25
- when 'String'
26
- [item, 'Edm.String']
27
- when 'Fixnum'
28
- [item, 'Edm.Int32']
29
- when 'Float'
30
- [item, 'Edm.Double']
31
- when 'TrueClass', 'FalseClass'
32
- [item, 'Edm.Boolean']
33
- when 'Time'
34
- [item.iso8601, 'Edm.DateTime']
35
- when 'File', 'StringIO'
36
- item.pos = 0
37
- [Base64.encode64(item.read), 'Edm.Binary']
38
- else
39
- [item, 'Edm.String']
40
- end
41
- end
42
- end
43
- end
44
- end
1
+ module WAZ
2
+ module Tables
3
+ class EdmTypeHelper
4
+ class << self
5
+ def parse_from(item)
6
+ return nil if !item.attributes['m:null'].nil? and item.attributes['m:null'] == 'true'
7
+ case item.attributes['m:type']
8
+ when 'Edm.Int16', 'Edm.Int32', 'Edm.Int64'
9
+ item.text.to_i
10
+ when 'Edm.Single', 'Edm.Double'
11
+ item.text.to_f
12
+ when 'Edm.Boolean'
13
+ item.text == 'true'
14
+ when 'Edm.DateTime'
15
+ Time.parse(item.text)
16
+ when 'Edm.Binary'
17
+ StringIO.new(Base64.decode64(item.text))
18
+ else
19
+ item.text
20
+ end
21
+ end
22
+
23
+ def parse_to(item)
24
+ case item.class.name
25
+ when 'String'
26
+ [item, 'Edm.String']
27
+ when 'Fixnum'
28
+ [item, 'Edm.Int32']
29
+ when 'Float'
30
+ [item, 'Edm.Double']
31
+ when 'TrueClass', 'FalseClass'
32
+ [item, 'Edm.Boolean']
33
+ when 'Time'
34
+ [item.iso8601, 'Edm.DateTime']
35
+ when 'File', 'StringIO'
36
+ item.pos = 0
37
+ [Base64.encode64(item.read), 'Edm.Binary']
38
+ else
39
+ [item, 'Edm.String']
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
45
  end
@@ -1,45 +1,45 @@
1
- module WAZ
2
- module Tables
3
- # This exception is raised while trying to create table that already exists.
4
- class TableAlreadyExists < WAZ::Storage::StorageException
5
- def initialize(name)
6
- super("The table #{name} already exists on your account.")
7
- end
8
- end
9
-
10
- # This exception is raised while trying to delete an unexisting table.
11
- class TableDoesNotExist < WAZ::Storage::StorageException
12
- def initialize(name)
13
- super("The specified table #{name} does not exist.")
14
- end
15
- end
16
-
17
- # This exception is raised when an invalid table name is provided.
18
- class InvalidTableName < WAZ::Storage::StorageException
19
- def initialize(name)
20
- super("The table name #{name} is invalid, it must start with at least one lower/upper characted, must be from 3 through 63 characters long and can have character or any digit starting from the second position")
21
- end
22
- end
23
-
24
- # This exception is raised when provided more than the 252 properties allowed by the Rest API.
25
- class TooManyProperties < WAZ::Storage::StorageException
26
- def initialize(total)
27
- super("The entity contains more properties than allowed (252). The entity has #{total} properties.")
28
- end
29
- end
30
-
31
- # This exception is raised when the specified entity already exists.
32
- class EntityAlreadyExists < WAZ::Storage::StorageException
33
- def initialize(row_key)
34
- super("The specified entity already exists. RowKey: #{row_key}")
35
- end
36
- end
37
-
38
- # This exception is raised while trying to delete an unexisting entity.
39
- class EntityDoesNotExist < WAZ::Storage::StorageException
40
- def initialize(key)
41
- super("The specified entity with #{key} does not exist.")
42
- end
43
- end
44
- end
1
+ module WAZ
2
+ module Tables
3
+ # This exception is raised while trying to create table that already exists.
4
+ class TableAlreadyExists < WAZ::Storage::StorageException
5
+ def initialize(name)
6
+ super("The table #{name} already exists on your account.")
7
+ end
8
+ end
9
+
10
+ # This exception is raised while trying to delete an unexisting table.
11
+ class TableDoesNotExist < WAZ::Storage::StorageException
12
+ def initialize(name)
13
+ super("The specified table #{name} does not exist.")
14
+ end
15
+ end
16
+
17
+ # This exception is raised when an invalid table name is provided.
18
+ class InvalidTableName < WAZ::Storage::StorageException
19
+ def initialize(name)
20
+ super("The table name #{name} is invalid, it must start with at least one lower/upper characted, must be from 3 through 63 characters long and can have character or any digit starting from the second position")
21
+ end
22
+ end
23
+
24
+ # This exception is raised when provided more than the 252 properties allowed by the Rest API.
25
+ class TooManyProperties < WAZ::Storage::StorageException
26
+ def initialize(total)
27
+ super("The entity contains more properties than allowed (252). The entity has #{total} properties.")
28
+ end
29
+ end
30
+
31
+ # This exception is raised when the specified entity already exists.
32
+ class EntityAlreadyExists < WAZ::Storage::StorageException
33
+ def initialize(row_key)
34
+ super("The specified entity already exists. RowKey: #{row_key}")
35
+ end
36
+ end
37
+
38
+ # This exception is raised while trying to delete an unexisting entity.
39
+ class EntityDoesNotExist < WAZ::Storage::StorageException
40
+ def initialize(key)
41
+ super("The specified entity with #{key} does not exist.")
42
+ end
43
+ end
44
+ end
45
45
  end
@@ -1,178 +1,178 @@
1
- module WAZ
2
- module Tables
3
- # This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
4
- # implementation. You can use this class to perform an specific operation that isn't provided by the current API.
5
- class Service
6
- include WAZ::Storage::SharedKeyCoreService
7
-
8
- DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
9
- DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
10
-
11
- # Creates a table on the current Windows Azure Storage account.
12
- def create_table(table_name)
13
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
14
-
15
- payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
16
- "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
17
- "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
18
- "<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
19
-
20
- begin
21
- execute :post, 'Tables', {}, default_headers, payload
22
- return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
23
- rescue RestClient::RequestFailed
24
- raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
25
- end
26
- end
27
-
28
- # Delete a table on the current Windows Azure Storage account.
29
- def delete_table(table_name)
30
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
31
- begin
32
- execute :delete, "Tables('#{table_name}')", {}, default_headers
33
- rescue RestClient::ResourceNotFound
34
- raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
35
- end
36
- end
37
-
38
- # Lists all existing tables on the current storage account.
39
- # remove Content-Type if it's not working
40
- def list_tables(next_table_name = nil)
41
- query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
42
- content = execute :get, "Tables", query ||= {}, default_headers
43
-
44
- doc = REXML::Document.new(content)
45
- tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
46
- { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
47
- :url => REXML::XPath.first(item, "id").text }
48
- end
49
-
50
- return tables, content.headers[:x_ms_continuation_nexttablename]
51
- end
52
-
53
- # Retrieves an existing table on the current storage account.
54
- # remove Content-Type if it's not working
55
- def get_table(table_name)
56
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
57
-
58
- begin
59
- content = execute :get, "Tables('#{table_name}')", {}, default_headers
60
- doc = REXML::Document.new(content)
61
- item = REXML::XPath.first(doc, "entry")
62
- return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
63
- :url => REXML::XPath.first(item, "id").text }
64
- rescue RestClient::ResourceNotFound
65
- raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
66
- end
67
- end
68
-
69
- # Insert a new entity on the provided table for the current storage account
70
- # TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
71
- def insert_entity(table_name, entity)
72
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
73
- raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
74
-
75
- begin
76
- response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
77
- return parse_response(response)
78
- rescue RestClient::RequestFailed
79
- raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
80
- end
81
- end
82
-
83
- # Update an existing entity on the current storage account.
84
- # TODO: handle specific errors
85
- def update_entity(table_name, entity)
86
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
87
- response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
88
- return parse_response(response)
89
- end
90
-
91
- # Merge an existing entity on the current storage account.
92
- # The Merge Entity operation updates an existing entity by updating the entity's properties.
93
- # This operation does not replace the existing entity, as the Update Entity operation does
94
- # TODO: handle specific errors
95
- def merge_entity(table_name, entity)
96
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
97
- response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
98
- return parse_response(response)
99
- end
100
-
101
- # Delete an existing entity in a table.
102
- def delete_entity(table_name, partition_key, row_key)
103
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
104
-
105
- begin
106
- execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
107
- rescue RestClient::ResourceNotFound
108
- raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
109
- raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
110
- end
111
- end
112
-
113
- # Retrieves an existing entity on the current storage account.
114
- # TODO: handle specific errors
115
- def get_entity(table_name, partition_key, row_key)
116
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
117
- response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
118
- return parse_response(response)
119
- end
120
-
121
- # Retrieves a set of entities on the current storage account for a given query.
122
- # When the :top => n is passed it returns only the first n rows that match with the query
123
- # Optional parameters:
124
- # * :headers a hash containing the request headers
125
- # * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
126
- # * :top limits the amount of fields for this query.
127
- # * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
128
- def query(table_name, options = {})
129
- raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
130
- query = {'$filter' => (options[:expression] or '') }
131
- query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
132
- query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
133
- response = execute :get, "#{table_name}()", query, default_headers
134
- continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
135
- parse_response(response, continuation_token)
136
- end
137
-
138
- private
139
- def generate_payload(table_name, entity)
140
- payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
141
- "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
142
- "<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
143
- "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
144
- "<content type=\"application/xml\"><m:properties>"
145
-
146
- entity.sort_by { |k| k.to_s }.each do |k,v|
147
- value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
148
- payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
149
- end
150
-
151
- payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
152
- "<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
153
- "</m:properties></content></entry>"
154
- return payload
155
- end
156
-
157
- def parse_response(response, continuation_token = nil)
158
- doc = REXML::Document.new(response)
159
- entities = REXML::XPath.each(doc, '//entry').map do |entry|
160
- fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
161
- { f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
162
- end
163
- Hash[*fields.collect {|h| h.to_a}.flatten]
164
- end
165
- entities = WAZ::Tables::TableArray.new(entities)
166
- entities.continuation_token = continuation_token
167
- return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
168
- end
169
-
170
- def default_headers
171
- { 'Date' => Time.new.httpdate,
172
- 'Content-Type' => 'application/atom+xml',
173
- 'DataServiceVersion' => '1.0;NetFx',
174
- 'MaxDataServiceVersion' => '1.0;NetFx' }
175
- end
176
- end
177
- end
178
- end
1
+ module WAZ
2
+ module Tables
3
+ # This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
4
+ # implementation. You can use this class to perform an specific operation that isn't provided by the current API.
5
+ class Service
6
+ include WAZ::Storage::SharedKeyCoreService
7
+
8
+ DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
9
+ DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
10
+
11
+ # Creates a table on the current Windows Azure Storage account.
12
+ def create_table(table_name)
13
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
14
+
15
+ payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
16
+ "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
17
+ "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
18
+ "<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
19
+
20
+ begin
21
+ execute :post, 'Tables', {}, default_headers, payload
22
+ return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
23
+ rescue RestClient::RequestFailed
24
+ raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
25
+ end
26
+ end
27
+
28
+ # Delete a table on the current Windows Azure Storage account.
29
+ def delete_table(table_name)
30
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
31
+ begin
32
+ execute :delete, "Tables('#{table_name}')", {}, default_headers
33
+ rescue RestClient::ResourceNotFound
34
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
35
+ end
36
+ end
37
+
38
+ # Lists all existing tables on the current storage account.
39
+ # remove Content-Type if it's not working
40
+ def list_tables(next_table_name = nil)
41
+ query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
42
+ content = execute :get, "Tables", query ||= {}, default_headers
43
+
44
+ doc = REXML::Document.new(content)
45
+ tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
46
+ { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
47
+ :url => REXML::XPath.first(item, "id").text }
48
+ end
49
+
50
+ return tables, content.headers[:x_ms_continuation_nexttablename]
51
+ end
52
+
53
+ # Retrieves an existing table on the current storage account.
54
+ # remove Content-Type if it's not working
55
+ def get_table(table_name)
56
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
57
+
58
+ begin
59
+ content = execute :get, "Tables('#{table_name}')", {}, default_headers
60
+ doc = REXML::Document.new(content)
61
+ item = REXML::XPath.first(doc, "entry")
62
+ return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
63
+ :url => REXML::XPath.first(item, "id").text }
64
+ rescue RestClient::ResourceNotFound
65
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
66
+ end
67
+ end
68
+
69
+ # Insert a new entity on the provided table for the current storage account
70
+ # TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
71
+ def insert_entity(table_name, entity)
72
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
73
+ raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
74
+
75
+ begin
76
+ response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
77
+ return parse_response(response)
78
+ rescue RestClient::RequestFailed
79
+ raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
80
+ end
81
+ end
82
+
83
+ # Update an existing entity on the current storage account.
84
+ # TODO: handle specific errors
85
+ def update_entity(table_name, entity)
86
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
87
+ response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
88
+ return parse_response(response)
89
+ end
90
+
91
+ # Merge an existing entity on the current storage account.
92
+ # The Merge Entity operation updates an existing entity by updating the entity's properties.
93
+ # This operation does not replace the existing entity, as the Update Entity operation does
94
+ # TODO: handle specific errors
95
+ def merge_entity(table_name, entity)
96
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
97
+ response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
98
+ return parse_response(response)
99
+ end
100
+
101
+ # Delete an existing entity in a table.
102
+ def delete_entity(table_name, partition_key, row_key)
103
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
104
+
105
+ begin
106
+ execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
107
+ rescue RestClient::ResourceNotFound
108
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
109
+ raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
110
+ end
111
+ end
112
+
113
+ # Retrieves an existing entity on the current storage account.
114
+ # TODO: handle specific errors
115
+ def get_entity(table_name, partition_key, row_key)
116
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
117
+ response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
118
+ return parse_response(response)
119
+ end
120
+
121
+ # Retrieves a set of entities on the current storage account for a given query.
122
+ # When the :top => n is passed it returns only the first n rows that match with the query
123
+ # Optional parameters:
124
+ # * :headers a hash containing the request headers
125
+ # * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
126
+ # * :top limits the amount of fields for this query.
127
+ # * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
128
+ def query(table_name, options = {})
129
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
130
+ query = {'$filter' => (options[:expression] or '') }
131
+ query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
132
+ query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
133
+ response = execute :get, "#{table_name}()", query, default_headers
134
+ continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
135
+ parse_response(response, continuation_token)
136
+ end
137
+
138
+ private
139
+ def generate_payload(table_name, entity)
140
+ payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
141
+ "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
142
+ "<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
143
+ "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
144
+ "<content type=\"application/xml\"><m:properties>"
145
+
146
+ entity.sort_by { |k| k.to_s }.each do |k,v|
147
+ value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
148
+ payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
149
+ end
150
+
151
+ payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
152
+ "<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
153
+ "</m:properties></content></entry>"
154
+ return payload
155
+ end
156
+
157
+ def parse_response(response, continuation_token = nil)
158
+ doc = REXML::Document.new(response)
159
+ entities = REXML::XPath.each(doc, '//entry').map do |entry|
160
+ fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
161
+ { f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
162
+ end
163
+ Hash[*fields.collect {|h| h.to_a}.flatten]
164
+ end
165
+ entities = WAZ::Tables::TableArray.new(entities)
166
+ entities.continuation_token = continuation_token
167
+ return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
168
+ end
169
+
170
+ def default_headers
171
+ { 'Date' => Time.new.httpdate,
172
+ 'Content-Type' => 'application/atom+xml',
173
+ 'DataServiceVersion' => '1.0;NetFx',
174
+ 'MaxDataServiceVersion' => '1.0;NetFx' }
175
+ end
176
+ end
177
+ end
178
+ end