lib/spiderfw/model/storage/base_storage.rb in spiderfw-0.6.23 vs lib/spiderfw/model/storage/base_storage.rb in spiderfw-0.6.24
- old
+ new
@@ -1,9 +1,12 @@
require 'spiderfw/model/storage/connection_pool'
module Spider; module Model; module Storage
+ # @abstract
+ # This class is subclassed by classes that interact with different storage backends.
+ # See also {Db::DbStorage}, {Document::DocumentStorage}.
class BaseStorage
include Spider::Logger
attr_reader :url
attr_accessor :instance_name
@@ -13,100 +16,140 @@
class << self
# An Hash of storage capabilities. The default for db storages is
# {:autoincrement => false, :sequences => true, :transactions => true}
# (The BaseStorage class provides file sequences in case the subclass does not support them.)
+ # @return [Hash]
attr_reader :capabilities
+ # @return [Symbol] A label for the storage's class.
def storage_type
:none
end
+ # @return [Sync] A Sync object to use for sequences
def sequence_sync
@sequence_sync ||= ::Sync.new
end
+ # @return [Array] Base types supported by the backend.
def base_types
Model.base_types
end
- # True if given named capability is supported by the Storage.
+ # @return [bool] True if given named capability is supported by the backend.
def supports?(capability)
@capabilities[capability]
end
- # Returns a new connection. Must be implemented by the subclasses; args are implementation specific.
+ # @abstract
+ # @return [Object] Returns a new connection. Must be implemented by the subclasses; args are implementation specific.
def new_connection(*args)
raise "Unimplemented"
end
+ # @abstract
+ # @return [Fixnum|nil] Maximum number of connections possible for this backend (or nil if unlimited)
def max_connections
nil
end
+ # @return [Hash] An Hash of connection pools for each backend.
def connection_pools
@pools ||= {}
end
+ # @param [*args] Storage specific arguments
+ # @return [Object] Retrieves a native connection to the backend from the {ConnectionPool}.
def get_connection(*args)
@pools ||= {}
@pools[args] ||= ConnectionPool.new(args, self)
@pools[args].get_connection
end
# Frees a connection, relasing it to the pool
+ # @param [Object] conn The connection
+ # @param [Array] conn_params An array of connection params that were used to create the connection.
+ # @return [void]
def release_connection(conn, conn_params)
return unless conn
return unless @pools && @pools[conn_params]
@pools[conn_params].release(conn)
end
# Removes a connection from the pool.
+ # @param [Object] conn The connection
+ # @param [Array] conn_params An array of connection params that were used to create the connection.
+ # @return [void]
def remove_connection(conn, conn_params)
return unless conn
return unless @pools && @pools[conn_params]
@pools[conn_params].remove(conn)
end
+ # @abstract
+ # Closes the native connection to the backend.
+ # @param [Object] conn The native connection
+ # @return [void]
def disconnect(conn)
raise "Virtual"
end
+ # @abstract
# Checks whether a connection is still alive. Must be implemented by subclasses.
+ # @param [Object] conn The native connection
+ # @return [void]
def connection_alive?(conn)
raise "Virtual"
end
+ # Copies capabilities on subclasses
+ # @param [Class<BaseStorage] subclass
+ # @return [void]
def inherited(subclass)
subclass.instance_variable_set("@capabilities", @capabilities)
end
end
-
+ # Creates a new storage instance.
+ # @param [String] url The backend-specific url for the connection
def initialize(url)
@url = url
@configuration = {}
parse_url(url)
end
+ # Sets configuration for the Storage
+ # @param [Hash] conf The configuration
+ # @return [void]
def configure(conf)
@configuration.merge!(conf.to_hash)
end
+ # @abstract
+ # Splits a backend-specific connection url into parts
+ # @param [String] url
+ # @return [Array]
def parse_url(url)
raise StorageException, "Unimplemented"
end
+ # @abstact
+ # @param [Class<BaseModel]
+ # @return [Mapper] Returns the instance of a mapper for the storage and the given model
def get_mapper(model)
raise StorageException, "Unimplemented"
end
+ # @param [Symbol] capability
+ # @return [bool] True if the backend supports the given capability
def supports?(capability)
self.class.supports?(capability)
end
+ # @return [Hash] An hash of thread-local values for this connection
def curr
var = nil
if Spider.conf.get('storage.shared_connection')
$STORAGES ||= {}
var = $STORAGES
@@ -118,28 +161,32 @@
var[:storages][self.class.storage_type][@connection_params] ||= {
:transaction_nesting => 0, :savepoints => []
}
end
+ # @return [ConnectionPool|nil] The ConnectionPool managing the current connection params
def connection_pool
self.class.connection_pools[@connection_params]
end
# Instantiates a new connection with current connection params.
+ # @return [void]
def connect
return self.class.get_connection(*@connection_params)
#Spider::Logger.debug("#{self.class.name} in thread #{Thread.current} acquired connection #{@conn}")
end
- # True if currently connected.
+ # @return [bool] True if currently connected.
def connected?
curr[:conn] != nil
end
# Returns the current connection, or creates a new one.
# If a block is given, will release the connection after yielding.
+ # @yield [Object] If a block is given, it is passed the connection, which is released after the block ends.
+ # @return [Object] The connection
def connection
curr[:conn] = connect
if block_given?
yield curr[:conn]
release # unless is_connected
@@ -147,19 +194,22 @@
else
return curr[:conn]
end
end
+ # @return [Hash] current connection attributes
def self.connection_attributes
@connection_attributes ||= {}
end
+ # @return [Hash] current connection attributes
def connection_attributes
self.class.connection_attributes[connection] ||= {}
end
# Releases the current connection to the pool.
+ # @return [void]
def release
# The subclass should check if the connection is alive, and if it is not call remove_connection instead
c = curr[:conn]
#Spider.logger.debug("#{self} in thread #{Thread.current} releasing #{curr[:conn]}")
curr[:conn] = nil
@@ -167,81 +217,110 @@
#Spider.logger.debug("#{self} in thread #{Thread.current} released #{curr[:conn]}")
return nil
#@conn = nil
end
- # Prepares a value for saving.
+ # Prepares a value which will be saved into the backend.
+ # @param [Class] type
+ # @param [Object] value
+ # @param [Symbol] save_mode :insert or :update or generic :save
+ # @return [Object] The prepared value
def value_for_save(type, value, save_mode)
return prepare_value(type, value)
end
- # Prepares a value that will be used in a condition.
+ # Prepares a value that will be used in a condition on the backend.
+ # @param [Class] type
+ # @param [Object] value
+ # @return [Object] The prepared value
def value_for_condition(type, value)
return prepare_value(type, value)
end
+ # Prepares a value coming from the backend for the mapper
+ # @param [Class] type
+ # @param [Object] value
+ # @return [Object] The prepared value
def value_to_mapper(type, value)
value
end
-
+ # Prepares a value that will be used by the backend (see also {#value_for_save} and {#value_for_condition},
+ # which by default call this method, but can be override to do more specific processiong).
+ # @param [Class] type
+ # @param [Object] value
+ # @return [Object] The prepared value
def prepare_value(type, value)
return value
end
+ # @return [bool] True if the other storage is of the same class, and has the same connection url
def ==(storage)
return false unless self.class == storage.class
return false unless self.url == storage.url
return true
end
-
+ # @return [bool] True if the backend support stransaction
def supports_transactions?
return self.class.supports?(:transactions)
end
+ # @return [bool] True if transactions are supported by the backend and enabled in the storage's configuration.
def transactions_enabled?
@configuration['enable_transactions'] && supports_transactions?
end
+ # Starts a new transaction on the backend
+ # @return [bool] True if a new transaction was started, false otherwise
def start_transaction
return unless transactions_enabled?
curr[:transaction_nesting] += 1
return savepoint("point#{curr[:savepoints].length}") if in_transaction?
Spider.logger.debug("#{self.class.name} starting transaction for connection #{connection.object_id}")
do_start_transaction
return true
end
- # May be implemented by subclasses.
+ # @abstract
+ # Implemented by subclasses to interact with the backend
def do_start_transaction
raise StorageException, "The current storage does not support transactions"
end
+ # Starts a transaction, or increases transaction nesting.
+ # @return [bool] True if a transaction was already active, false otherwise
def in_transaction
if in_transaction?
curr[:transaction_nesting] += 1
return true
else
start_transaction
return false
end
end
+ # @return [bool] True if a transaction is currently active
def in_transaction?
return false
end
+ # Commits the current transaction
+ # @return [bool] True if the transaction was successfully committed, false if transactions are not enabled
+ # (Raises a StorageException if transactions are supported but were not started)
def commit
return false unless transactions_enabled?
raise StorageException, "Commit without a transaction" unless in_transaction?
return curr[:savepoints].pop unless curr[:savepoints].empty?
commit!
end
+ # Commits the current transaction, or decreases transaction nesting.
+ # @return [bool] True if the transaction was successfully committed, false if transactions are not enabled
+ # (Raises a StorageException if transactions are supported but were not started)
def commit_or_continue
return false unless transactions_enabled?
raise StorageException, "Commit without a transaction" unless in_transaction?
if curr[:transaction_nesting] == 1
commit
@@ -250,43 +329,61 @@
else
curr[:transaction_nesting] -= 1
end
end
+ # Commits current transaction, resets transaction nesting, and releases the connection.
+ # @return [void]
def commit!
Spider.logger.debug("#{self.class.name} commit connection #{curr[:conn].object_id}")
curr[:transaction_nesting] = 0
do_commit
release
end
+ # @abstract
+ # Implemented by subclasses to interact with the backend
+ # @return [void]
def do_commit
raise StorageException, "The current storage does not support transactions"
end
+ # Rolls back the current transaction. Raises an error if in a nested transaction.
+ # @return [void]
def rollback
raise "Can't rollback in a nested transaction" if curr[:transaction_nesting] > 1
return rollback_savepoint(curr[:savepoints].last) unless curr[:savepoints].empty?
rollback!
end
+ # Rolls back the current transaction, regardless of transaction nesting, and releases the connection
+ # @return [void]
def rollback!
curr[:transaction_nesting] = 0
Spider.logger.debug("#{self.class.name} rollback")
do_rollback
curr[:savepoints] = []
release
end
+ # @abstract
+ # Implemented by subclasses to interact with the backend
+ # @return [void]
def do_rollback
raise StorageException, "The current storage does not support transactions"
end
+ # Creates a new savepoint
+ # @param [String] name
+ # @return [void]
def savepoint(name)
curr[:savepoints] << name
end
+ # Rolls back a savepoint
+ # @param [String] name
+ # @return [void]
def rollback_savepoint(name=nil)
if name
curr[:savepoints] = curr[:savepoints][0,(curr[:savepoints].index(name))]
name
else
@@ -294,34 +391,51 @@
end
end
# Utility methods
+ # @param [String] name Sequence name
+ # @return [String] Path to the sequence file
def sequence_file_path(name)
path = File.join(Spider.paths[:var], 'sequences', name)
return path
end
+ # @param [String] name Sequence name
+ # @return [bool] True if the sequence file exists
def sequence_exists?(name)
File.exist?(sequence_file_path(name))
end
+ # Creates a new sequence
+ # @param [String] name Sequence name
+ # @param [Fixnum] start
+ # @param [Fixnum] increment
+ # @return [void]
def create_sequence(name, start=1, increment=1)
sequence_next(name, start-1, increment)
end
+ # @return [String] A new UUID
def generate_uuid
Spider::DataTypes::UUID.generate
end
-
+ # Updates a sequence
+ # @param [String] name Sequence name
+ # @param [Fixnum] val New value for the sequence
+ # @return [Fixnum] New value for the sequence
def update_sequence(name, val)
# not an alias because the set value behaviour of next_sequence isn't expected in subclasses
sequence_next(name, val)
end
# Increments a named sequence and returns the new value
+ # @param [String] name Sequence name
+ # @param [Fixnum] newval New value for the sequence
+ # @param [Fixnum] increment
+ # @return [Fixnum] New value for the sequence
def sequence_next(name, newval=nil, increment=1)
path = sequence_file_path(name)
FileUtils.mkpath(File.dirname(path))
self.class.sequence_sync.lock(::Sync::EX)
if newval
@@ -358,9 +472,10 @@
###############################
# Exceptions #
###############################
+ # Exception for Storage related errors
class StorageException < RuntimeError
end