mirror of
https://github.com/mistergibson/gxg-framework.git
synced 2026-08-15 03:46:00 -07:00
5502 lines
255 KiB
Ruby
5502 lines
255 KiB
Ruby
# require sequel here to ensure other mods are underneath it
|
|
require "sequel"
|
|
require "securerandom"
|
|
require "openssl"
|
|
require "base64"
|
|
require "net/ldap"
|
|
module GxG
|
|
DB_SAFETY = ::Mutex.new
|
|
DB = {:cache => {}, :formats => {}, :authority => nil, :administrator => nil, :roles => {}}
|
|
#
|
|
module Database
|
|
# Class Place-holder Definitions
|
|
class DetachedArray
|
|
#
|
|
end
|
|
class DetachedHash
|
|
#
|
|
end
|
|
class PersistedArray
|
|
#
|
|
end
|
|
class PersistedHash
|
|
#
|
|
end
|
|
#
|
|
def self.element_tables()
|
|
{
|
|
:unspecified => nil,
|
|
:element_boolean => [::TrueClass, ::FalseClass, ::NilClass],
|
|
:element_integer => [::Integer],
|
|
:element_float => [::Float],
|
|
:element_bigdecimal => [::BigDecimal],
|
|
:element_datetime => [::DateTime],
|
|
:element_text => [::String],
|
|
:element_binary => [::GxG::ByteArray],
|
|
:element_array => [::Array],
|
|
:element_hash => [::Hash]
|
|
}
|
|
end
|
|
#
|
|
def self.valid_field_classes()
|
|
[::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray]
|
|
end
|
|
def self.element_table_index(the_key=:unspecified)
|
|
(::GxG::Database::element_tables().keys.index(the_key.to_sym) || 0)
|
|
end
|
|
#
|
|
def self.element_table_by_index(the_index=0)
|
|
(::GxG::Database::element_tables().keys[(the_index)] || :unspecified)
|
|
end
|
|
#
|
|
def self.element_table_for_instance(the_instance=nil)
|
|
result = :unspecified
|
|
#
|
|
table = ::GxG::Database::element_tables()
|
|
table.keys.each do |the_key|
|
|
unless the_key == :unspecified
|
|
if the_instance.is_any?(table[(the_key)])
|
|
result = the_key
|
|
break
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def self.persistable?(the_object)
|
|
result = true
|
|
unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
if the_object.is_any?(::Hash, ::Array)
|
|
the_object.search do |item, selector, container|
|
|
if result
|
|
if ::GxG::Database::element_table_for_instance(item) == :unspecified
|
|
result = false
|
|
break
|
|
end
|
|
end
|
|
nil
|
|
end
|
|
else
|
|
if ::GxG::Database::element_table_for_instance(the_object) == :unspecified
|
|
result = false
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.iterative_detached_persist(old_root=nil)
|
|
# New PersistedArray, or PersistedHash interface:
|
|
#
|
|
result = nil
|
|
begin
|
|
# db open?
|
|
# write permissions on db?
|
|
unless old_root.is_any?(::Array, ::Hash)
|
|
raise ArgumentError, "You MUST provide either an Array or a Hash."
|
|
end
|
|
unless GxG::Database::persistable?(old_root)
|
|
raise ArgumentError, "The object you are attempting to persist contains a non-persistable item."
|
|
end
|
|
#
|
|
if old_root.is_a?(::Array)
|
|
original_partner = ::GxG::Database::DetachedArray.new()
|
|
#
|
|
end
|
|
if old_root.is_a?(::Hash)
|
|
original_partner = ::GxG::Database::DetachedHash.new()
|
|
end
|
|
#
|
|
paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}]
|
|
children_of = Proc.new do |the_parent=nil|
|
|
list = []
|
|
paring_data.each do |node|
|
|
if node[:parent].object_id == the_parent.object_id
|
|
list << node
|
|
end
|
|
end
|
|
list
|
|
end
|
|
#
|
|
parent_of = Proc.new do |the_parent=nil|
|
|
output = nil
|
|
paring_data.each do |entry|
|
|
if entry[:object].object_id == the_parent.object_id
|
|
output = entry
|
|
end
|
|
end
|
|
output
|
|
end
|
|
find_partner = Proc.new do |the_object|
|
|
found_partner = nil
|
|
paring_data.each do |the_record|
|
|
if the_record.is_a?(::Hash)
|
|
if the_record[:object].object_id == the_object.object_id
|
|
found_partner = the_record[:partner]
|
|
break
|
|
end
|
|
end
|
|
end
|
|
found_partner
|
|
end
|
|
# build paring data:
|
|
delegate_permission = nil
|
|
old_root.search do |the_value, the_selector, the_container|
|
|
if the_value.is_a?(::Array)
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedArray.new()}
|
|
else
|
|
if the_value.is_a?(::Hash)
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedHash.new()}
|
|
else
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value}
|
|
end
|
|
end
|
|
end
|
|
#
|
|
# Assign objects to structure in order by parent / parent_selector
|
|
link_db = [(paring_data[0])]
|
|
while link_db.size > 0
|
|
entry = link_db.shift
|
|
if entry.is_a?(::Hash)
|
|
if entry[:object].is_any?(::Array, ::Hash)
|
|
# get children and assign
|
|
children = children_of.call(entry[:object])
|
|
children.each do |child|
|
|
entry[:partner][(child[:parent_selector])] = child[:partner]
|
|
if child[:partner].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash)
|
|
link_db << child
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result = original_partner
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:object => old_root}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.detached_format_find(the_criteria={})
|
|
result = nil
|
|
tests = {}
|
|
[:uuid, :ufs, :title, :version].each do |the_key|
|
|
if the_criteria[(the_key)]
|
|
tests[(the_key)] = the_criteria[(the_key)]
|
|
end
|
|
end
|
|
GxG::DB[:formats].each_pair do |uuid,record|
|
|
score = 0
|
|
tests.keys.each do |the_test|
|
|
if record[(the_test)] == tests[(the_test)]
|
|
score += 1
|
|
end
|
|
end
|
|
if tests.keys.size == score
|
|
result = record
|
|
break
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.detached_format_load(the_criteria={}, &block)
|
|
# If already loaded and block provided: return an Array of format records.
|
|
# Load any missing formats from server, and call block if provided.
|
|
# Note: results are in NO particular order (DO NOT INDEX into the result set from source criteria)
|
|
success = false
|
|
needed = []
|
|
results = []
|
|
if the_criteria.is_a?(::Hash)
|
|
loaded = ::GxG::Database::detached_format_find(the_criteria)
|
|
if loaded
|
|
results << loaded
|
|
if block.respond_to?(:call)
|
|
block.call(results)
|
|
end
|
|
success = true
|
|
else
|
|
needed << the_criteria
|
|
end
|
|
else
|
|
if the_criteria.is_a?(::Array)
|
|
the_criteria.each do |the_specifier|
|
|
loaded = ::GxG::Database::detached_format_find(the_specifier)
|
|
if loaded
|
|
results << loaded
|
|
else
|
|
needed << the_specifier
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if needed.size > 0
|
|
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
|
|
needed.each do |the_specifier|
|
|
format_record = ::GxG::DB[:roles][:format].format_load({:uuid => the_specifier.to_s.to_sym})
|
|
if format_record
|
|
GxG::DB[:formats][(format_record[:uuid].to_sym)] = format_record
|
|
results << format_record
|
|
end
|
|
end
|
|
if block.respond_to?(:call)
|
|
block.call(results)
|
|
end
|
|
success = true
|
|
end
|
|
else
|
|
if results.size > 0
|
|
if block.respond_to?(:call)
|
|
block.call(results)
|
|
end
|
|
success = true
|
|
end
|
|
end
|
|
#
|
|
success
|
|
end
|
|
#
|
|
def self.process_detached_import(import_manifest=nil, options={})
|
|
result = []
|
|
if import_manifest.is_a?(::Hash)
|
|
if import_manifest[:formats].size > 0
|
|
# import formats: GxG::DB[:formats]
|
|
import_manifest[:formats].keys.each do |the_key|
|
|
unless GxG::DB[:formats][(the_key)]
|
|
# Review do ::Hash.gxg_import on format content
|
|
format_record = import_manifest[:formats][(the_key)]
|
|
if format_record[:version].is_a?(::String)
|
|
format_record[:version] = BigDecimal.new(format_record[:version])
|
|
end
|
|
format_record[:content] = ::Hash.gxg_import(format_record[:content])
|
|
GxG::DB[:formats][(the_key)] = format_record
|
|
end
|
|
end
|
|
end
|
|
#
|
|
if import_manifest[:records].size > 1
|
|
import_manifest[:records].each do |the_record|
|
|
result << GxG::Database::DetachedHash::import(the_record)
|
|
end
|
|
else
|
|
result << GxG::Database::DetachedHash::import(import_manifest[:records][0])
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.process_import(import_manifest=nil, credential=nil, options={})
|
|
# sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={})
|
|
result = false
|
|
if import_manifest.is_a?(::Hash) && ::GxG::valid_uuid?(credential) && options.is_a?(::Hash)
|
|
options = {:role => :data}.merge!(options)
|
|
database = ::GxG::DB[:roles][(options[:role])]
|
|
if database.is_a?(::GxG::Database::Database)
|
|
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
|
|
unless database == ::GxG::DB[:roles][:format]
|
|
::GxG::DB[:roles][:format].sync_import(credential, {:formats => import_manifest[:formats], :records => []}, options)
|
|
end
|
|
end
|
|
#
|
|
result = database.sync_import(credential, import_manifest, options)
|
|
else
|
|
log_warn("No suitable database of role #{options[:role].inspect} could be found for the import request : Ignoring Request")
|
|
end
|
|
else
|
|
log_warn("Invalid parameters to import request : Ignoring Request")
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.as_import_record(records=[], options={})
|
|
result = {:formats => {}, :records => []}
|
|
if records.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash)
|
|
records = [(records)]
|
|
end
|
|
if records.is_any?(::Array, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray)
|
|
records.each do |the_object|
|
|
if the_object.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash)
|
|
object_record = the_object.export()
|
|
object_record.search do |item,selector,container|
|
|
if selector == :format || selector == :constraint
|
|
if item.to_s.size > 0
|
|
format_uuid = item
|
|
unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash)
|
|
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
|
|
format_sample = ::GxG::DB[:roles][:format].format_load({:uuid => format_uuid.to_s.to_sym})
|
|
format_sample[:content] = format_sample[:content].gxg_export()
|
|
result[:formats][(format_uuid.to_s.to_sym)] = format_sample
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result[:records] << object_record
|
|
#
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def self.connect(the_url, options={})
|
|
# TODO: require :credential (access privs)
|
|
result = nil
|
|
begin
|
|
# check for URI ipv6 [format] compatibility
|
|
db_url = ::URI::parse(the_url.to_s)
|
|
the_path = db_url.path.to_s
|
|
if the_path == ""
|
|
the_path = db_url.hostname.to_s
|
|
end
|
|
if ::RUBY_ENGINE == "jruby"
|
|
# jdbc:sqlite::memory:
|
|
# jdbc:postgresql://localhost/database?user=username
|
|
# jdbc:mysql://localhost/test?user=root&password=root
|
|
# jdbc:h2:mem:
|
|
# jdbc:hsqldb:mem:mymemdb
|
|
# jdbc:derby:memory:myDb;create=true
|
|
# jdbc:sqlserver://localhost;database=sequel_test;integratedSecurity=true
|
|
# jdbc:jtds:sqlserver://localhost/sequel_test;user=sequel_test;password=sequel_test
|
|
# jdbc:oracle:thin:user/password@localhost:1521:database
|
|
# jdbc:db2://localhost:3700/database:user=user;password=password;
|
|
# jdbc:sqlanywhere://localhost?DBN=Test;UID=user;PWD=password
|
|
case db_url.scheme.to_sym
|
|
when :sqlite, :sqlite3
|
|
if ["/memory", "memory"].include?(the_path)
|
|
result = Sequel.connect("jdbc:sqlite::memory:")
|
|
else
|
|
result = Sequel.connect("jdbc:sqlite:" + the_path)
|
|
end
|
|
when :mysql, :mysql2, :mariadb
|
|
result = Sequel.connect(("jdbc:mysql://" + db_url.hostname.to_s + "/" + db_url.path.to_s.split("/").last.to_s + "?user=" + db_url.user.to_s + "&password=" + db_url.password.to_s + "&serverTimezone=UTC"))
|
|
else
|
|
raise Exception, "As-yet unsupported adapter type --> Under Construction"
|
|
end
|
|
#
|
|
else
|
|
case db_url.scheme.to_sym
|
|
when :sqlite, :sqlite3
|
|
if ["/memory", "memory"].include?(the_path)
|
|
result = Sequel.connect("sqlite::memory:")
|
|
else
|
|
result = Sequel.connect("sqlite://" + the_path)
|
|
end
|
|
else
|
|
# auto-forward to mysql2 gem if mysql:// scheme
|
|
if db_url.scheme.to_s == "mysql"
|
|
#
|
|
result = Sequel.connect({:adapter => "mysql2", :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
|
|
#
|
|
else
|
|
#
|
|
result = Sequel.connect({:adapter => (db_url.scheme.to_s), :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
|
|
#
|
|
end
|
|
end
|
|
end
|
|
result = ::GxG::Database::Database.new(result, options.merge({:url => (db_url)}))
|
|
rescue => the_error
|
|
log_error({:error => the_error, :parameters => {:url => (the_url), :options => (options)}})
|
|
end
|
|
result
|
|
end
|
|
# External Authority (Such as LDAP)
|
|
class ExternalAuthority
|
|
def alive?()
|
|
true
|
|
end
|
|
end
|
|
#
|
|
class LDAPAuthority < ::GxG::Database::ExternalAuthority
|
|
#
|
|
def initialize(settings={})
|
|
# Settings: {:host => "localhost", :port => 389, :base_dn => "cn=admin, dc=my-domain, dc=com", :password => "secret-word"}
|
|
@active = false
|
|
@connector = ::Net::LDAP.new
|
|
@connector.host = ::TCPSocket.getaddress(settings[:host].to_s)
|
|
@connector.port = settings[:port].to_s.to_i
|
|
@connector.auth settings[:base_dn].to_s, settings[:password].to_s
|
|
if @connector.bind
|
|
@active = true
|
|
else
|
|
raise "Unable to bind to LDAP host."
|
|
end
|
|
self
|
|
end
|
|
#
|
|
def open?()
|
|
@active
|
|
end
|
|
#
|
|
end
|
|
# Database class proper
|
|
class Database
|
|
def self.connect(the_url, options={})
|
|
::GxG::Database::connect(the_url, options)
|
|
end
|
|
#
|
|
def self.element_tables()
|
|
{
|
|
:unspecified => nil,
|
|
:element_boolean => [::TrueClass, ::FalseClass, ::NilClass],
|
|
:element_integer => [::Integer],
|
|
:element_float => [::Float],
|
|
:element_bigdecimal => [::BigDecimal],
|
|
:element_datetime => [::DateTime],
|
|
:element_text => [::String],
|
|
:element_binary => [::GxG::ByteArray],
|
|
:element_array => [::Array],
|
|
:element_hash => [::Hash]
|
|
}
|
|
end
|
|
#
|
|
def self.valid_field_classes()
|
|
[::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray]
|
|
end
|
|
#
|
|
def self.element_table_index(the_key=:unspecified)
|
|
(::GxG::Database::Database::element_tables().keys.index(the_key.to_sym) || 0)
|
|
end
|
|
#
|
|
def self.element_table_by_index(the_index=0)
|
|
(::GxG::Database::Database::element_tables().keys[(the_index)] || :unspecified)
|
|
end
|
|
#
|
|
def self.element_table_for_instance(the_instance=nil)
|
|
# Review : rewrite - in use
|
|
result = :unspecified
|
|
#
|
|
table = ::GxG::Database::Database::element_tables()
|
|
table.keys.each do |the_key|
|
|
unless the_key == :unspecified
|
|
if the_instance.is_any?(table[(the_key)])
|
|
result = the_key
|
|
break
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def db_migration()
|
|
# Review : Consider adding support for a ::Symbol class as well. (ease-of-use implications later)
|
|
# Internal mapping of the database format
|
|
# Datatypes - See: http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html
|
|
# Column Options - See: http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-add_column
|
|
# See also : http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack
|
|
{
|
|
:element_text => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
|
|
},
|
|
:text_page => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:content => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
|
|
},
|
|
:element_binary => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
|
|
},
|
|
:binary_page => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:content => {:type => :blob, :options => {:size => 65536}, :bytes => 65536}
|
|
},
|
|
:element_array => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:constraint => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:array_elements => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
|
|
:element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
|
|
:time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
|
|
},
|
|
:element_hash => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:hash_properties => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:property => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
|
|
:element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
|
|
:time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
|
|
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
|
|
},
|
|
:element_locks => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:type => {:type => :varchar, :options => {:size => 40,:default => "", :null => false}, :bytes => 40},
|
|
:reservation => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
|
|
},
|
|
:trash => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
|
|
},
|
|
:users => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:user_id => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
|
|
:password_hash => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:roles => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:seo => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:user_roles => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:user_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:role_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:permissions => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:file_permissions => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:file_id => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
},
|
|
:formats => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:type => {:type => :varchar, :options => {:size => 40, :null => false, :default => "structure"}, :bytes => 40},
|
|
:ufs => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099},
|
|
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
|
|
:mime_types => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
|
|
},
|
|
:settings => {
|
|
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
|
|
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
|
|
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
|
|
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
|
|
}
|
|
}
|
|
end
|
|
#
|
|
def db_format(format_options={})
|
|
# MySql stuff:
|
|
# ALTER TABLE tbl_name MAX_ROWS=1000000000 AVG_ROW_LENGTH=nnn;
|
|
# See: https://dev.mysql.com/doc/refman/5.0/en/table-size-limit.html
|
|
result = true
|
|
#
|
|
begin
|
|
if @connector.is_a?(::Sequel::Database)
|
|
case @scheme
|
|
when :sqlite
|
|
@connector.run("PRAGMA page_size=65536")
|
|
@connector.run("PRAGMA max_page_count=281474976710656")
|
|
@connector.run("PRAGMA encoding='UTF-8'")
|
|
when :mysql, :mysql2
|
|
@connector.run("SET GLOBAL max_allowed_packet = 17825792;")
|
|
@connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
|
|
@connector.run("SET collation_connection = 'utf8_general_ci';")
|
|
when :postgres
|
|
# place holder
|
|
end
|
|
migration = db_migration()
|
|
migration.keys.each do |the_table|
|
|
unless @connector.table_exists?(the_table)
|
|
@connector.create_table(the_table) do
|
|
primary_key :dbid
|
|
end
|
|
end
|
|
used_bytes = 0
|
|
#
|
|
existing_columns = @connector[(the_table)].columns
|
|
migration[(the_table)].keys.each do |the_column|
|
|
used_bytes += (migration[(the_table)][(the_column)][:bytes]).to_i
|
|
if existing_columns.include?(the_column)
|
|
# TODO: figure out a way to inspect columns for attributes -> detailed verification
|
|
else
|
|
# collation for mysql
|
|
if @scheme == :mysql
|
|
# Note: might have to specify :collate first, use -> {:collate => ""}.merge(migration[(the_table)][(the_column)][:options])
|
|
if migration[(the_table)][(the_column)][:type] == :varchar
|
|
# migration[(the_table)][(the_column)][:options] = {:collate => "utf8_general_ci"}.merge(migration[(the_table)][(the_column)][:options])
|
|
migration[(the_table)][(the_column)][:options][:collate] = "utf8_general_ci"
|
|
end
|
|
if migration[(the_table)][(the_column)][:type] == :blob
|
|
# migration[(the_table)][(the_column)][:options] = {:collate => "binary"}.merge(migration[(the_table)][(the_column)][:options])
|
|
# Note: MySQL 5 doesn't like putting collation on blobs! (blows up)
|
|
# migration[(the_table)][(the_column)][:options][:collate] = "binary"
|
|
end
|
|
else
|
|
end
|
|
# do the deed
|
|
@connector.add_column((the_table), (the_column), (migration[(the_table)][(the_column)][:type]), (migration[(the_table)][(the_column)][:options]))
|
|
#
|
|
end
|
|
end
|
|
#
|
|
if @scheme == :mysql
|
|
# @connector.run("ALTER TABLE #{the_table.to_s} MAX_ROWS=18446744073709551616 AVG_ROW_LENGTH=#{used_bytes.to_s};")
|
|
end
|
|
#
|
|
end
|
|
else
|
|
raise Exception, "Invalid db connection"
|
|
end
|
|
rescue Exception => the_error
|
|
result = false
|
|
log_error({:error => the_error, :parameters => format_options})
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def db_formatted?()
|
|
result = true
|
|
#
|
|
begin
|
|
if @connector.is_a?(::Sequel::Database)
|
|
migration = db_migration()
|
|
migration.keys.each do |the_table|
|
|
unless @connector.table_exists?(the_table)
|
|
result = false
|
|
break
|
|
end
|
|
#columns
|
|
existing_columns = @connector[(the_table)].columns
|
|
migration[(the_table)].keys.each do |the_column|
|
|
unless existing_columns.include?(the_column)
|
|
result = false
|
|
break
|
|
end
|
|
end
|
|
#
|
|
unless (result)
|
|
break
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Invalid db connection"
|
|
end
|
|
rescue Exception => the_error
|
|
result = false
|
|
log_error({:error => the_error, :parameters => nil})
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def open?()
|
|
begin
|
|
@connector.synchronize do |connection|
|
|
@active = @connector.valid_connection?(connection)
|
|
end
|
|
rescue => the_error
|
|
@active = false
|
|
log_error({:error => the_error, :parameters => nil})
|
|
end
|
|
@active
|
|
end
|
|
#
|
|
def close()
|
|
# place holder for now.
|
|
if @authority
|
|
@authority.db_unregister(self)
|
|
end
|
|
end
|
|
#
|
|
def connector()
|
|
@connector
|
|
end
|
|
#
|
|
#
|
|
def to_uri()
|
|
::URI.parse(@url.to_s)
|
|
end
|
|
#
|
|
def inspect()
|
|
"<Database: #{self.to_uri()}>"
|
|
end
|
|
# ### Permission Handling methods
|
|
def db_permissions
|
|
@base_permissions.clone
|
|
end
|
|
# ### Authority support
|
|
def db_register(the_db)
|
|
if the_db.is_a?(::GxG::Database::Database)
|
|
unless @db_list.find_index(the_db)
|
|
@db_list << the_db
|
|
end
|
|
end
|
|
end
|
|
#
|
|
def db_unregister(the_db)
|
|
if the_db.is_a?(::GxG::Database::Database)
|
|
if @db_list.find_index(the_db)
|
|
@db_list.delete_at(@db_list.find_index(the_db))
|
|
end
|
|
end
|
|
end
|
|
#
|
|
def db_list()
|
|
result = []
|
|
@db_list.each do |the_db|
|
|
result << the_db
|
|
end
|
|
result
|
|
end
|
|
# ### User, Role, and Group methods
|
|
#
|
|
def user_manifest(include_password=false)
|
|
result = []
|
|
if @authority
|
|
result = @authority.user_manifest(include_password)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if include_password == true
|
|
dataset = @connector[:users].select(:uuid, :user_id, :password_hash, :version).all
|
|
else
|
|
dataset = @connector[:users].select(:uuid, :user_id, :version).all
|
|
end
|
|
dataset.each do |entry|
|
|
result << entry
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_id_available?(the_user_id)
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_id_available?(the_user_id)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if the_user_id.to_s.size <= 256
|
|
if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count == 0
|
|
result = true
|
|
end
|
|
else
|
|
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_exist?(the_user_id)
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_exist?(the_user_id)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if ::GxG::valid_uuid?(the_user_id)
|
|
if @connector[:users].filter({:uuid => (the_user_id.to_s)}).count > 0
|
|
result = true
|
|
end
|
|
else
|
|
if the_user_id.to_s.size <= 256
|
|
if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count > 0
|
|
result = true
|
|
end
|
|
else
|
|
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_credential(the_user_id="", the_password="")
|
|
result = nil
|
|
if @authority
|
|
result = @authority.user_credential(the_user_id, the_password)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if the_user_id.to_s.size <= 256
|
|
if the_password.to_s.size > 0
|
|
record = @connector[:users].select(:uuid, :user_id, :password_hash).where({:user_id => (the_user_id.to_s)}).first
|
|
if record
|
|
salt = record[:password_hash].split("\t")[0].to_s
|
|
pbkdf2 = ::Base64.decode64(record[:password_hash].split("\t")[1].to_s)
|
|
tester = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password.to_s, salt, 1000, pbkdf2.length)
|
|
if pbkdf2 == tester
|
|
result = record[:uuid].to_sym
|
|
else
|
|
log_warn("Password attempt invalid for #{the_user_id}")
|
|
end
|
|
else
|
|
raise Exception, "UserID not found: #{the_user_id}"
|
|
end
|
|
else
|
|
raise Exception, "Empty password not supported"
|
|
end
|
|
else
|
|
raise Exception, "Oversized UserID attempted: #{the_user_id}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_create(the_user_id="", the_password="", use_credential=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_create(the_user_id, the_password)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if the_user_id.to_s.size <= 256
|
|
if the_password.to_s.size > 0
|
|
if self.user_id_available?(the_user_id)
|
|
if ::GxG::valid_uuid?(use_credential)
|
|
the_uuid = use_credential.to_s
|
|
if (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
|
|
raise Exception, "That credential UUID is already in use"
|
|
end
|
|
else
|
|
the_uuid = ::GxG::uuid_generate()
|
|
while (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
|
|
the_uuid = ::GxG::uuid_generate()
|
|
end
|
|
end
|
|
salt = ::SecureRandom.base64(24)
|
|
pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password, salt, 1000, 24)
|
|
the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
|
|
@connector[:users].insert({:uuid => (the_uuid), :user_id => (the_user_id.to_s), :password_hash => (the_pass_hash)})
|
|
result = true
|
|
else
|
|
raise Exception, "UserID not available: #{the_user_id}"
|
|
end
|
|
else
|
|
raise ArgumentError, "Empty password not supported"
|
|
end
|
|
else
|
|
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_update(credential=nil, user_id=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_update(credential, user_id)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(credential)
|
|
if self.user_id_available?(user_id.to_s)
|
|
record = @connector[:users].filter({:uuid => (credential.to_s)}).first
|
|
if record
|
|
@connector[:users].filter({:uuid => (credential.to_s)}).update({:user_id => (user_id), :version => (record[:version] + 0.0001)})
|
|
result = true
|
|
else
|
|
raise Exception, "User record not found"
|
|
end
|
|
else
|
|
log_warn("UserID is not available: #{user_id.to_s}")
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => credential, :user_id => user_id}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_set_password(credential=nil, old_password="", new_password="")
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_set_password(credential, old_password, new_password)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(credential)
|
|
if new_password.to_s.size > 0
|
|
if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
|
|
record = @connector[:users].filter({:uuid => (credential.to_s)}).first
|
|
if record
|
|
if self.user_credential(record[:user_id], old_password).to_s == credential.to_s
|
|
salt = ::SecureRandom.base64(24)
|
|
pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(new_password, salt, 1000, 24)
|
|
the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
|
|
@connector[:users].filter({:uuid => (credential.to_s)}).update({:password_hash => (the_pass_hash), :version => (BigDecimal(record[:version].to_s) + 0.0001)})
|
|
result = true
|
|
else
|
|
log_warn("Invalid password change attempted for #{credential}")
|
|
end
|
|
else
|
|
raise Exception, "Could not retrieve user record: #{credential}"
|
|
end
|
|
else
|
|
raise Exception, "No user with that credential exists"
|
|
end
|
|
else
|
|
raise ArgumentError, "Empty password not supported"
|
|
end
|
|
else
|
|
raise ArgumentError, "Invalid credential argument: #{credential}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => credential}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_destroy(credential=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.user_destroy(credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(credential)
|
|
if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
|
|
the_list = self.db_list()
|
|
the_list << (self)
|
|
the_list.each do |the_db|
|
|
the_db.connector()[:permissions].filter({:credential => (credential.to_s)}).delete
|
|
the_db.connector()[:user_roles].filter({:user_uuid => (credential.to_s)}).delete
|
|
end
|
|
@connector[:users].filter({:uuid => (credential.to_s)}).delete
|
|
result = true
|
|
end
|
|
else
|
|
raise ArgumentError, "Invalid credential argument: #{credential}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => credential}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_fetch(search_options={})
|
|
result = nil
|
|
if @authority
|
|
result = @authority.user_fetch(search_options)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
result = @connector[:users].filter(search_options).first
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def user_roles(credential=nil)
|
|
result = []
|
|
if @authority
|
|
result = @authority.user_roles(credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if ::GxG::valid_uuid?(credential)
|
|
memberships = @connector[:user_roles].select(:role_uuid).where({:user_uuid => (credential.to_s)})
|
|
pull_list = []
|
|
memberships.each do |record|
|
|
unless pull_list.find_index(record[:role_uuid].to_sym)
|
|
pull_list << record[:role_uuid].to_sym
|
|
end
|
|
end
|
|
pull_list.each do |role_uuid|
|
|
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)})
|
|
if record
|
|
result << {:credential => (role_uuid), :title => (record[:title]), :seo => (record[:seo])}
|
|
end
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_manifest()
|
|
result = []
|
|
#
|
|
if @authority
|
|
result = @authority.roles()
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
@connector[:roles].all.each do |record|
|
|
result << {:uuid => record[:uuid].to_s.to_sym, :title => record[:title], :seo => record[:seo], :version => BigDecimal(record[:version].to_s)}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def role_exist?(credential=nil)
|
|
result = nil
|
|
if @authority
|
|
result = @authority.role_exist?(credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if ::GxG::valid_uuid?(credential)
|
|
if @connector[:roles].filter({:uuid => (credential.to_s)}).count > 0
|
|
result = true
|
|
end
|
|
else
|
|
if @connector[:roles].filter({:title => (credential.to_s)}).count > 0
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_create(title="", seo="")
|
|
result = nil
|
|
if @authority
|
|
result = @authority.role_create(title,seo)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
#
|
|
if title.to_s.size <= 256
|
|
#
|
|
if title.to_s.size == 0
|
|
raise ArgumentError, "You MUST supply a title for the role."
|
|
end
|
|
unless seo.to_s.size <= 256
|
|
raise ArgumentError, "Oversized SEO attempted (256 Character Limit): #{seo}"
|
|
end
|
|
if seo.to_s.size == 0
|
|
seo = title.downcase.gsub(" ","_")[0..255]
|
|
end
|
|
the_uuid = ::GxG::uuid_generate()
|
|
while (@connector[:roles].filter({:uuid => (the_uuid.to_s)}).count > 0)
|
|
the_uuid = ::GxG::uuid_generate()
|
|
end
|
|
@connector[:roles].insert({:uuid => (the_uuid.to_s), :title => (title.to_s), :seo => (seo.to_s)})
|
|
result = the_uuid.to_sym
|
|
else
|
|
raise ArgumentError, "Oversized title attempted (256 Character Limit): #{title}"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:title => (title), :seo => (seo)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_update(role_uuid=nil, data={})
|
|
# data: :title, :seo
|
|
result = false
|
|
if @authority
|
|
result = @authority.role_update(role_uuid,data)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
unless data.is_a?(::Hash)
|
|
raise Exception, "Update data must be passed in a Hash"
|
|
end
|
|
if ::GxG::valid_uuid?(role_uuid)
|
|
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
|
|
if record
|
|
the_title = (data[:title] || record[:title].to_s)
|
|
the_seo = (data[:seo] || the_title.downcase.gsub(" ", "_"))
|
|
#
|
|
@connector[:roles].filter({:uuid => (role_uuid.to_s)}).update({:title => (the_title), :seo => (the_seo), :version => (BigDecimal(record[:version].to_s) + 0.0001)})
|
|
result = true
|
|
else
|
|
raise Exception, "Attempted to update a non-existent role: #{role_uuid}"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :data => (data)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_destroy(role_uuid=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.role_destroy(role_uuid)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(role_uuid)
|
|
# cannot destroy if still linked as group's default role
|
|
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
|
|
if record
|
|
the_list = self.db_list()
|
|
the_list << (self)
|
|
the_list.each do |the_db|
|
|
the_db.connector()[:permissions].filter({:credential => (role_uuid.to_s)}).delete
|
|
the_db.connector()[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).delete
|
|
end
|
|
@connector[:roles].filter({:uuid => (role_uuid.to_s)}).delete
|
|
result = true
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_add_user(role_uuid=nil, credential=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.role_add_user(role_uuid, credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(role_uuid)
|
|
if ::GxG::valid_uuid?(credential)
|
|
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count == 0
|
|
@connector[:user_roles].insert({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)})
|
|
end
|
|
result = true
|
|
else
|
|
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_remove_user(role_uuid=nil, credential=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.role_remove_user(role_uuid, credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
begin
|
|
if ::GxG::valid_uuid?(role_uuid)
|
|
if ::GxG::valid_uuid?(credential)
|
|
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
|
|
@connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).delete
|
|
end
|
|
result = true
|
|
else
|
|
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_member?(role_uuid=nil, credential=nil)
|
|
result = false
|
|
if @authority
|
|
result = @authority.role_member?(role_uuid, credential)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
begin
|
|
if ::GxG::valid_uuid?(role_uuid)
|
|
if ::GxG::valid_uuid?(credential)
|
|
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
|
|
result = true
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_members(role_uuid=nil)
|
|
result = []
|
|
if @authority
|
|
result = @authority.role_members(role_uuid)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
if GxG::valid_uuid?(role_uuid)
|
|
@connector[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).each do |the_record|
|
|
unless result.include?(the_record[:user_uuid].to_s.to_sym)
|
|
result << the_record[:user_uuid].to_s.to_sym
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def role_fetch(search_options={})
|
|
result = nil
|
|
if @authority
|
|
result = @authority.role_fetch(search_options)
|
|
else
|
|
if self.alive?
|
|
if self.open?
|
|
result = @connector[:roles].filter(search_options).first
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
# ### Raw Permission Handling methods
|
|
def element_permissions(table=:unspecified, dbid=0, options={})
|
|
result = []
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "dbid must be specified with a Integer"
|
|
end
|
|
unless options.is_a?(::Hash)
|
|
raise ArgumentError, "options must be specified with a Hash"
|
|
end
|
|
credential = options[:credential]
|
|
if credential
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
end
|
|
if table.is_a?(::Integer)
|
|
table = ::GxG::Database::Database::element_table_by_index(table)
|
|
end
|
|
# Review : change in permission policy
|
|
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
if [:element_array, :element_hash].include?(table)
|
|
if credential
|
|
list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)})
|
|
else
|
|
list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)})
|
|
end
|
|
list.each do |record|
|
|
# [:execute, :rename, :move, :destroy, :create, :write, :read]
|
|
entry = {:credential => (record[:credential].to_sym), :table => (record[:element_table].to_sym), :dbid => (record[:elementid]), :permissions => {}}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if record[(the_permission)] == 1
|
|
entry[:permissions][(the_permission)] = true
|
|
else
|
|
entry[:permissions][(the_permission)] = false
|
|
end
|
|
end
|
|
result << entry
|
|
end
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def revoke_element_permissions(table=:unspecified, dbid=0, credential=nil)
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "dbid must be specified with a Integer"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
if table.is_a?(::Integer)
|
|
table = ::GxG::Database::Database::element_table_by_index(table)
|
|
end
|
|
# Review : change in permission policy
|
|
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
if [:element_array, :element_hash].include?(table)
|
|
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
|
|
result = true
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
#
|
|
def element_permissions_manifest(table=:unspecified, dbid=0, options={})
|
|
result = []
|
|
self.element_permissions(table,dbid,options).each do |entry|
|
|
the_record = {:credential => nil, :permissions => {}, :details => {}}
|
|
#
|
|
the_record[:credential] = entry[:credential]
|
|
the_record[:permissions] = entry[:permissions]
|
|
if self.role_exist?(entry[:credential])
|
|
role = self.role_fetch({:uuid => entry[:credential].to_s})
|
|
group = self.group_fetch({:uuid => role[:group_uuid].to_s})
|
|
the_record[:details][:role_title] = role[:title]
|
|
the_record[:details][:group] = group[:uuid].to_s.to_sym
|
|
the_record[:details][:group_title] = group[:title]
|
|
else
|
|
if self.user_exist?(entry[:credential])
|
|
user = self.user_fetch({:uuid => entry[:credential].to_s})
|
|
the_record[:details][:user_title] = user[:user_id]
|
|
else
|
|
log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
|
|
the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
|
|
end
|
|
end
|
|
result << the_record
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_permissions_by_uuid(the_uuid=nil,options={})
|
|
result = []
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = self.element_permissions_manifest(:element_hash,(found[:dbid]),options)
|
|
else
|
|
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = self.element_permissions_manifest(:element_array,(found[:dbid]),options)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def revoke_permissions_by_uuid(the_uuid=nil, credential=nil)
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = self.revoke_element_permissions(:element_hash,(found[:dbid]),credential)
|
|
else
|
|
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = self.revoke_element_permissions(:element_array,(found[:dbid]),credential)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def assign_element_permission(table=:unspecified, dbid=0, credential=nil, permissions={})
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "dbid must be specified with a Integer"
|
|
end
|
|
unless permissions.is_a?(::Hash)
|
|
raise ArgumentError, "permissions must be specified with a Hash"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
if table.is_a?(::Integer)
|
|
table = ::GxG::Database::Database::element_table_by_index(table)
|
|
end
|
|
# Review : change in permission policy
|
|
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
if [:element_array, :element_hash].include?(table)
|
|
record = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).first
|
|
if record
|
|
# update
|
|
the_update = {:execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if record[(the_permission)] == 1
|
|
the_update[(the_permission)] = 1
|
|
end
|
|
end
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if permissions[(the_permission)].is_a?(::TrueClass)
|
|
the_update[(the_permission)] = 1
|
|
end
|
|
if permissions[(the_permission)].is_a?(::FalseClass)
|
|
the_update[(the_permission)] = 0
|
|
end
|
|
end
|
|
the_update[:version] = record[:version] + 0.0001
|
|
@connector[:permissions].filter({:dbid => (record[:dbid])}).update(the_update)
|
|
else
|
|
# create
|
|
new_record = {:credential => (credential.to_s), :element_table => (table.to_s), :elementid => (dbid), :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if permissions[(the_permission)]
|
|
new_record[(the_permission)] = 1
|
|
end
|
|
end
|
|
@connector[:permissions].insert(new_record)
|
|
end
|
|
result = true
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential), :permissions => (permissions)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def extend_element_permissions(source_table=:unspecified, source_dbid=0, dest_table=:unspecified, dest_dbid=0)
|
|
permissions = self.element_permissions(source_table, source_dbid)
|
|
permissions.each do |entry|
|
|
if [:persisted_array, :persisted_hash].include?(dest_table)
|
|
self.assign_element_permission(dest_table, dest_dbid, entry[:credential], entry[:permissions])
|
|
end
|
|
end
|
|
end
|
|
#
|
|
def remove_element_permissions(table=:unspecified, dbid=0, options={})
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "dbid must be specified with a Integer"
|
|
end
|
|
unless options.is_a?(::Hash)
|
|
raise ArgumentError, "permissions must be specified with a Hash"
|
|
end
|
|
credential = options[:credential]
|
|
if credential
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
end
|
|
if table.is_a?(::Integer)
|
|
table = ::GxG::Database::Database::element_table_by_index(table)
|
|
end
|
|
# Review : change in permission policy
|
|
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
if [:element_array, :element_hash].include?(table)
|
|
if credential
|
|
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
|
|
else
|
|
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)}).delete
|
|
end
|
|
result = true
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
|
|
end
|
|
result
|
|
end
|
|
# ### Effective Permission Handling methods
|
|
# ### VFS File permission mappings
|
|
def vfs_permission_exist?(file_id=nil, credential=nil)
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "file_id must be specified with a String"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
#
|
|
if @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).count > 0
|
|
result = true
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def vfs_permission(file_id=nil, credential=nil)
|
|
result = []
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "dbid must be specified with a String"
|
|
end
|
|
if credential
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
end
|
|
#
|
|
if credential
|
|
list = @connector[:file_permissions].filter({:file_id => (file_id.to_s), :credential => (credential.to_s)})
|
|
else
|
|
list = @connector[:file_permissions].filter({:file_id => (file_id.to_s)})
|
|
end
|
|
list.each do |record|
|
|
# [:execute, :rename, :move, :destroy, :create, :write, :read]
|
|
entry = {:credential => (record[:credential].to_sym), :permissions => {}}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if record[(the_permission)] == 1
|
|
entry[:permissions][(the_permission)] = true
|
|
else
|
|
entry[:permissions][(the_permission)] = false
|
|
end
|
|
end
|
|
result << entry
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def vfs_permission_manifest(file_id=nil, credential=nil)
|
|
result = []
|
|
self.vfs_permission(file_id, credential).each do |entry|
|
|
the_record = {:credential => nil, :permissions => {}, :details => {}}
|
|
#
|
|
the_record[:credential] = entry[:credential]
|
|
the_record[:permissions] = entry[:permissions]
|
|
if self.role_exist?(entry[:credential])
|
|
role = self.role_fetch({:uuid => entry[:credential].to_s})
|
|
the_record[:details][:role_title] = role[:title]
|
|
else
|
|
if self.user_exist?(entry[:credential])
|
|
user = self.user_fetch({:uuid => entry[:credential].to_s})
|
|
the_record[:details][:user_title] = user[:user_id]
|
|
else
|
|
log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
|
|
the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
|
|
end
|
|
end
|
|
result << the_record
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def effective_vfs_permission(file_id=nil, credential=nil)
|
|
result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "file_id must be specified with a String"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
# ### VFS Permissions
|
|
raw_permissions = []
|
|
credentials = ["00000000-0000-4000-0000-000000000000"]
|
|
if self.user_exist?(credential)
|
|
credentials << credential
|
|
role_list = self.user_roles(credential)
|
|
role_list.each do |entry|
|
|
credentials << entry[:credential]
|
|
end
|
|
#
|
|
else
|
|
if self.role_exist?(credential)
|
|
credentials << credential
|
|
else
|
|
raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID"
|
|
end
|
|
end
|
|
credentials.each do |entry|
|
|
manifest = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => (entry.to_s)})
|
|
manifest.each do |record|
|
|
new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if record[(the_permission)] == 1
|
|
new_permission[(the_permission)] = true
|
|
end
|
|
end
|
|
raw_permissions << new_permission
|
|
end
|
|
end
|
|
raw_permissions.each do |entry|
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if entry[(the_permission)]
|
|
result[(the_permission)] = true
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def create_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "file_id must be specified with a String"
|
|
end
|
|
unless permission.is_a?(::Hash)
|
|
raise ArgumentError, "permission must be specified with a Hash"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
#
|
|
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
|
|
if record
|
|
update = true
|
|
dbid = record.delete(:dbid)
|
|
else
|
|
update = false
|
|
dbid = nil
|
|
record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
|
|
end
|
|
permission.keys.each do |the_permission_key|
|
|
if permission[(the_permission_key)] == true
|
|
record[(the_permission_key)] = 1
|
|
else
|
|
record[(the_permission_key)] = 0
|
|
end
|
|
end
|
|
if update == true
|
|
@connector[:file_permissions].filter({:dbid => dbid}).update(record)
|
|
else
|
|
@connector[:file_permissions].insert(record)
|
|
end
|
|
result = true
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def update_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "file_id must be specified with a String"
|
|
end
|
|
unless permission.is_a?(::Hash)
|
|
raise ArgumentError, "permission must be specified with a Hash"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
#
|
|
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
|
|
if record
|
|
update = true
|
|
dbid = record.delete(:dbid)
|
|
else
|
|
update = false
|
|
dbid = nil
|
|
record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
|
|
# Review : should we update the version ??
|
|
end
|
|
permission.keys.each do |the_permission_key|
|
|
if permission[(the_permission_key)] == true
|
|
record[(the_permission_key)] = 1
|
|
else
|
|
record[(the_permission_key)] = 0
|
|
end
|
|
end
|
|
if update == true
|
|
@connector[:file_permissions].filter({:dbid => dbid}).update(record)
|
|
else
|
|
@connector[:file_permissions].insert(record)
|
|
end
|
|
result = true
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def destroy_vfs_permission(file_id=nil, credential=nil)
|
|
result = false
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless file_id.is_a?(::String)
|
|
raise ArgumentError, "file_id must be specified with a String"
|
|
end
|
|
# ### Review : make more efficient
|
|
if credential
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
|
|
if record
|
|
@connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).delete
|
|
end
|
|
else
|
|
record = @connector[:file_permissions].filter({:file_id => file_id.to_s}).first
|
|
if record
|
|
@connector[:file_permissions].filter({:file_id => file_id.to_s}).delete
|
|
end
|
|
end
|
|
result = true
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
# ### DB Objects
|
|
def effective_element_permission(table=:unspecified, dbid=0, credential=nil)
|
|
result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
begin
|
|
unless self.alive?
|
|
raise Exception, "Database inactive"
|
|
end
|
|
unless self.open?
|
|
raise Exception, "Database not available"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "dbid must be specified with a Integer"
|
|
end
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
if table.is_a?(::Integer)
|
|
table = ::GxG::Database::Database::element_table_by_index(table)
|
|
end
|
|
# Review : change in permission policy
|
|
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
if [:element_array, :element_hash].include?(table)
|
|
# is credential a user or role?
|
|
raw_permissions = []
|
|
# Include PUBLIC credential
|
|
credentials = ["00000000-0000-4000-0000-000000000000"]
|
|
#
|
|
if self.user_exist?(credential)
|
|
credentials << credential
|
|
role_list = self.user_roles(credential)
|
|
role_list.each do |entry|
|
|
credentials << entry[:credential]
|
|
end
|
|
#
|
|
else
|
|
if self.role_exist?(credential)
|
|
credentials << credential
|
|
else
|
|
raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID"
|
|
end
|
|
end
|
|
credentials.each do |entry|
|
|
manifest = @connector[:permissions].filter({:credential => (entry.to_s), :element_table => (table.to_s), :elementid => (dbid)})
|
|
manifest.each do |record|
|
|
new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if record[(the_permission)] == 1
|
|
new_permission[(the_permission)] = true
|
|
end
|
|
end
|
|
raw_permissions << new_permission
|
|
end
|
|
end
|
|
permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
raw_permissions.each do |entry|
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if entry[(the_permission)]
|
|
permission[(the_permission)] = true
|
|
end
|
|
end
|
|
end
|
|
db_limits = self.db_permissions()
|
|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
|
|
if db_limits[(the_permission)] and permission[(the_permission)]
|
|
result[(the_permission)] = true
|
|
end
|
|
end
|
|
#
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def effective_uuid_permission(uuid=nil, credential=nil)
|
|
result = nil
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
address = nil
|
|
record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
|
|
else
|
|
record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
|
|
end
|
|
end
|
|
if address
|
|
result = self.effective_element_permission(address[:table], address[:dbid], credential)
|
|
else
|
|
# ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
|
|
already_checked = [(self)]
|
|
::GxG::DB[:roles].each_pair do |the_db_role, the_database|
|
|
unless already_checked.include?(the_database)
|
|
record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
|
|
result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
|
|
break
|
|
else
|
|
record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
|
|
result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
|
|
break
|
|
end
|
|
end
|
|
already_checked << the_database
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential)}})
|
|
end
|
|
result
|
|
end
|
|
# ### Data Handling methods
|
|
#
|
|
def element_exists?(the_table=:unspecified, the_dbid=0)
|
|
result = false
|
|
if the_table.is_a?(::Integer)
|
|
the_table = ::GxG::Database::Database::element_table_by_index(the_table)
|
|
end
|
|
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(the_table)
|
|
begin
|
|
record = @connector[(the_table)].select(:dbid).where({:dbid => (the_dbid)}).first
|
|
if record
|
|
result = true
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_retrieve(the_table=:unspecified, the_filter={}, options={})
|
|
result = []
|
|
begin
|
|
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary,:binary_page, :element_array, :element_hash].include?(the_table)
|
|
#
|
|
if the_filter.is_a?(::Hash)
|
|
if options[:order]
|
|
raw_result = @connector[(the_table)].filter(the_filter).order(options[:order])
|
|
else
|
|
raw_result = @connector[(the_table)].filter(the_filter)
|
|
end
|
|
# Review : UTF-8 Encoding Error Fix
|
|
if options[:single_record] == true
|
|
result = raw_result.first
|
|
# utf8 fix
|
|
if the_table == :text_page
|
|
if result[:content].is_a?(::String)
|
|
if result[:content].base64?
|
|
result[:content] = result[:content].decode64
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
# utf8 fix
|
|
raw_result.each do |the_record|
|
|
if the_record[:content].is_a?(::String)
|
|
if the_record[:content].base64?
|
|
the_record[:content] = the_record[:content].decode64
|
|
end
|
|
end
|
|
result << the_record
|
|
end
|
|
# result = raw_result
|
|
end
|
|
#
|
|
else
|
|
raise ArguementError, "You MUST provide a record selection filter as a Hash. "
|
|
end
|
|
else
|
|
raise ArgumentError, "The table specified is unknown - Check your code."
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :options => options}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_create(the_table=:unspecified, the_record=nil)
|
|
# Review : rewrite - in use
|
|
result = nil
|
|
begin
|
|
unless the_table.is_a?(::Symbol)
|
|
raise ArgumentError, "You MUST provide the table specifier as a Symbol."
|
|
end
|
|
unless the_record.is_a?(::Hash)
|
|
raise ArgumentError, "You MUST provide a record in the form of a Hash."
|
|
end
|
|
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
|
|
# Review : UTF8 fix
|
|
begin
|
|
result = @connector[(the_table)].insert(the_record)
|
|
rescue Exception
|
|
if the_table == :text_page
|
|
the_record[:content] = the_record[:content].encode64
|
|
end
|
|
result = @connector[(the_table)].insert(the_record)
|
|
end
|
|
else
|
|
raise ArgumentError, "The table specified is unknown - Check your code."
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (the_table), :record => (the_record)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_update(the_table=:unspecified, the_filter={}, the_record=nil)
|
|
result = false
|
|
begin
|
|
unless the_table.is_a?(::Symbol)
|
|
raise ArgumentError, "You MUST provide the table specifier as a Symbol."
|
|
end
|
|
unless the_filter.is_a?(::Hash)
|
|
raise ArgumentError, "You MUST provide a selection filter in the form of a Hash."
|
|
end
|
|
unless the_record.is_a?(::Hash)
|
|
raise ArgumentError, "You MUST provide a record in the form of a Hash."
|
|
end
|
|
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
|
|
# Review : shim to eliminate UTF-8 encoding errors
|
|
begin
|
|
@connector[(the_table)].filter(the_filter).update(the_record)
|
|
rescue Exception
|
|
if the_table == :text_page
|
|
the_record[:content] = the_record[:content].encode64
|
|
end
|
|
@connector[(the_table)].filter(the_filter).update(the_record)
|
|
end
|
|
result = true
|
|
else
|
|
raise ArgumentError, "The table specified is unknown - Check your code."
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :record => the_record}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def structure_detach(the_uuid=nil)
|
|
if ::GxG::valid_uuid?(the_uuid)
|
|
record = self.structural_link(the_uuid)
|
|
if record
|
|
record[:database].connector()[(record[:table])].filter({:dbid => record[:dbid]}).update({:element => "element_boolean", :element_boolean => -1, :element_hash_uuid => "", :element_array_uuid => ""})
|
|
true
|
|
else
|
|
false
|
|
end
|
|
else
|
|
false
|
|
end
|
|
end
|
|
#
|
|
def structural_link(the_uuid=nil)
|
|
# Review : rewrite - in use
|
|
result = nil
|
|
if ::GxG::valid_uuid?(the_uuid)
|
|
search_queue = [(self)]
|
|
::GxG::DB[:roles].each_pair do |the_role, the_database|
|
|
unless search_queue.include?(the_database)
|
|
search_queue << the_database
|
|
end
|
|
end
|
|
search_queue.each do |the_database|
|
|
found = the_database.connector()[:hash_properties].filter({:element_hash_uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
|
|
break
|
|
end
|
|
found = the_database.connector()[:hash_properties].filter({:element_array_uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
|
|
break
|
|
end
|
|
found = the_database.connector()[:array_elements].filter({:element_hash_uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
|
|
break
|
|
end
|
|
found = the_database.connector()[:array_elements].filter({:element_array_uuid => the_uuid.to_s}).first
|
|
if found
|
|
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
|
|
break
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def structure_attached?(the_uuid=nil)
|
|
# Review : rewrite - in use
|
|
result = false
|
|
record = self.structural_link(the_uuid)
|
|
if record
|
|
result = true
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_in_use?(db_table=:unspecified, dbid = 0)
|
|
# Review : rewrite - in use for now.
|
|
# Review : rewrite - todo (alter to reflect the new architecture : perhaps limit to structures only)
|
|
unless db_table.is_any?(::Integer, ::Symbol)
|
|
raise ArgumentError, "Table needs to be specified as a Symbol, or a table code Integer"
|
|
end
|
|
unless dbid.is_a?(::Integer)
|
|
raise ArgumentError, "Record ID needs to be specified with an Integer"
|
|
end
|
|
result = false
|
|
begin
|
|
if db_table == :element_hash || db_table == :element_array
|
|
# Review : make more efficient in later revs of this.
|
|
the_record = @connector[(db_table)].filter({:dbid => (dbid)}).select(:uuid).first
|
|
if the_record
|
|
if @connector[:hash_properties].filter({:element_hash_uuid => the_record[:uuid]}).count > 0 || @connector[:array_elements].filter({:element_array_uuid => the_record[:uuid]}).count > 0
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table_code => (db_table), :dbid => (dbid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_parent_address(the_table=:unspecified, the_dbid=0)
|
|
result = nil
|
|
if self.element_in_use?(the_table,the_dbid)
|
|
begin
|
|
link = @connector[:hash_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
|
|
if link
|
|
record = @connector[:element_hash].select(:dbid).where({:uuid => link[:parent_uuid]}).first
|
|
if record
|
|
result = {:table => :element_hash, :dbid => (record[:dbid])}
|
|
end
|
|
else
|
|
link = @connector[:array_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
|
|
if link
|
|
record = @connector[:element_array].select(:dbid).where({:uuid => link[:parent_uuid]}).first
|
|
if record
|
|
result = {:table => :element_array, :dbid => (record[:dbid])}
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_manifest(the_table=:unspecified, the_dbid=0, everything=false)
|
|
# Review : rewrite - in use
|
|
# returns an Array of tables and dbid pairs and sub-item pairs if they exist
|
|
result = []
|
|
#
|
|
begin
|
|
unless the_dbid > 0
|
|
raise ArgumentError, "dbid specified must be greater than zero"
|
|
end
|
|
case the_table
|
|
when :element_text
|
|
the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
|
|
if the_record
|
|
result << {:table => :element_text, :dbid => the_record[:dbid]}
|
|
if everything
|
|
the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
|
|
the_pages.each do |entry|
|
|
result << {:table => :text_page, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
when :element_binary
|
|
the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
|
|
if the_record
|
|
result << {:table => :element_binary, :dbid => the_record[:dbid]}
|
|
if everything
|
|
the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
|
|
the_pages.each do |entry|
|
|
result << {:table => :binary_page, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
when :element_array, :element_hash
|
|
record_db = [{:table => the_table, :dbid => the_dbid}]
|
|
while record_db.size > 0 do
|
|
item = record_db.shift
|
|
case item[:table]
|
|
when :element_text
|
|
the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
|
|
if the_record
|
|
result << {:table => :element_text, :dbid => the_record[:dbid]}
|
|
if everything
|
|
the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
|
|
the_pages.each do |entry|
|
|
result << {:table => :text_page, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
when :element_binary
|
|
the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
|
|
if the_record
|
|
result << {:table => :element_binary, :dbid => the_record[:dbid]}
|
|
if everything
|
|
the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
|
|
the_pages.each do |entry|
|
|
result << {:table => :binary_page, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
when :element_array
|
|
result << {:table => item[:table], :dbid => item[:dbid]}
|
|
array_record = @connector[:element_array].select(:uuid).where({:dbid => item[:dbid]}).first
|
|
if array_record
|
|
@connector[:array_elements].filter({:parent_uuid => (array_record[:uuid])}).order(:ordinal).each do |entry|
|
|
if entry[:element_text_uuid].size > 0
|
|
the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_binary_uuid].size > 0
|
|
the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_array_uuid].size > 0
|
|
the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_hash_uuid].size > 0
|
|
the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
result << {:table => :array_elements, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
when :element_hash
|
|
result << {:table => item[:table], :dbid => item[:dbid]}
|
|
hash_record = @connector[:element_hash].select(:uuid).where({:dbid => item[:dbid]}).first
|
|
if hash_record
|
|
@connector[:hash_properties].filter({:parent_uuid => (hash_record[:uuid])}).order(:ordinal).each do |entry|
|
|
if entry[:element_text_uuid].size > 0
|
|
the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_binary_uuid].size > 0
|
|
the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_array_uuid].size > 0
|
|
the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
if entry[:element_hash_uuid].size > 0
|
|
the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
|
|
if the_temp_record
|
|
record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
|
|
end
|
|
end
|
|
result << {:table => :hash_properties, :dbid => entry[:dbid]}
|
|
end
|
|
end
|
|
else
|
|
the_record = @connector[(item[:table])].select(:dbid).where({:dbid => (item[:dbid])}).first
|
|
if the_record
|
|
result << {:table => item[:table], :dbid => item[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
#
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
|
|
# Review : rewrite for new arch.
|
|
# Review : rewrite - in use
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
# if credential has :destroy permission on this element?
|
|
if ::GxG::valid_uuid?(credential)
|
|
if [:persisted_array, :persisted_hash].include?(table.to_s.to_sym)
|
|
go_ahead = self.effective_element_permission(table, dbid, credential)[:destroy]
|
|
else
|
|
go_ahead = true
|
|
end
|
|
# xxx Quick fix : BETA: clean all this up!
|
|
if go_ahead
|
|
# Review : DB overhaul / rewrite - allow delete if object has write lock in place? I think not. (return false)
|
|
# Note: BETA - bring back read-locks? it was 9x slower with them ... not sure.
|
|
elements = self.element_manifest(table, dbid, false)
|
|
elements.each do |entry|
|
|
#
|
|
unless self.element_in_trash?(entry[:table], entry[:dbid])
|
|
@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
end
|
|
#
|
|
#if @connector[:element_locks].filter({:type => "read", :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
|
|
# put in trash
|
|
#unless self.element_in_trash?(entry[:table], entry[:dbid])
|
|
#@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
#end
|
|
#else
|
|
# delete now
|
|
#manifest = self.element_manifest(entry[:table], entry[:dbid], true)
|
|
#manifest.each do |item|
|
|
#@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
|
|
#@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
|
|
#end
|
|
#end
|
|
end
|
|
result = true
|
|
else
|
|
raise Exception, "You do not have permission to destory this element"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential), :table => (table), :dbid => (dbid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def destroy_by_uuid(credential=nil, the_uuid=nil)
|
|
# Review : rewrite - in use
|
|
# element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
|
|
# .effective_uuid_permission(data["location"].to_s.to_sym,@credential)
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:destroy] == true
|
|
if ::GxG::valid_uuid?(credential)
|
|
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
self.element_destroy(credential, :element_hash,(found[:dbid]))
|
|
result = true
|
|
else
|
|
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
self.element_destroy(credential, :element_array,(found[:dbid]))
|
|
result = true
|
|
end
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential), :uuid => (the_uuid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_byte_size(table=:unspecified, dbid=0)
|
|
#
|
|
result = 0
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
migration = self.db_migration()
|
|
elements = self.element_manifest(table, dbid, true)
|
|
elements.each do |entry|
|
|
table_definition = migration[(entry[:table])]
|
|
if table_definition
|
|
table_definition.keys.each do |the_field|
|
|
result += table_definition[(the_field)][:bytes].to_i
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def byte_size_by_uuid(the_uuid=nil)
|
|
result = 0
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result += self.element_byte_size(:element_hash,(found[:dbid]))
|
|
else
|
|
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
|
|
if found
|
|
result += self.element_byte_size(:element_array,(found[:dbid]))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def persistable?(the_object)
|
|
result = true
|
|
unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
if the_object.is_any?(::Hash, ::Array)
|
|
the_object.search do |item, selector, container|
|
|
if result
|
|
if ::GxG::Database::Database::element_table_for_instance(item) == :unspecified
|
|
result = false
|
|
break
|
|
end
|
|
end
|
|
nil
|
|
end
|
|
else
|
|
if ::GxG::Database::Database::element_table_for_instance(the_object) == :unspecified
|
|
result = false
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def iterative_persist(old_root=nil, credential=nil, options={})
|
|
# New PersistedArray, or PersistedHash interface:
|
|
# mount element: {:database => <database>, :uuid => <uuid>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
|
|
# create element: {:database => <database>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
|
|
#
|
|
result = nil
|
|
begin
|
|
# db open?
|
|
# write permissions on db?
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You MUST provide a valid UUID as a credential."
|
|
end
|
|
unless old_root.is_any?(::Array, ::Hash)
|
|
raise ArgumentError, "You MUST provide either an Array or a Hash."
|
|
end
|
|
unless self.persistable?(old_root)
|
|
raise ArgumentError, "The object you are attempting to persist contains a non-persistable item."
|
|
end
|
|
if old_root.is_a?(::Array)
|
|
original_partner = ::GxG::Database::PersistedArray.new({:database => self, :credential => credential}, options)
|
|
original_partner.get_reservation()
|
|
self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
|
|
#
|
|
end
|
|
if old_root.is_a?(::Hash)
|
|
original_partner = ::GxG::Database::PersistedHash.new({:database => self, :credential => credential}, options)
|
|
original_partner.get_reservation()
|
|
self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
|
|
end
|
|
if old_root.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
|
|
# TODO: support subsuming already persisted objects.
|
|
end
|
|
paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}]
|
|
children_of = Proc.new do |the_parent=nil|
|
|
list = []
|
|
paring_data.each do |node|
|
|
if node[:parent].object_id == the_parent.object_id
|
|
list << node
|
|
end
|
|
end
|
|
list
|
|
end
|
|
#
|
|
parent_of = Proc.new do |the_parent=nil|
|
|
output = nil
|
|
paring_data.each do |entry|
|
|
if entry[:object].object_id == the_parent.object_id
|
|
output = entry
|
|
end
|
|
end
|
|
output
|
|
end
|
|
find_partner = Proc.new do |the_object|
|
|
found_partner = nil
|
|
paring_data.each do |the_record|
|
|
if the_record.is_a?(::Hash)
|
|
if the_record[:object].object_id == the_object.object_id
|
|
found_partner = the_record[:partner]
|
|
break
|
|
end
|
|
end
|
|
end
|
|
found_partner
|
|
end
|
|
# build paring data:
|
|
delegate_permission = nil
|
|
old_root.search do |the_value, the_selector, the_container|
|
|
if the_value.is_a?(::Array)
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedArray.new({:database => self, :credential => credential})}
|
|
else
|
|
if the_value.is_a?(::Hash)
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedHash.new({:database => self, :credential => credential})}
|
|
else
|
|
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value}
|
|
end
|
|
end
|
|
end
|
|
# Assign objects to structure in order by parent / parent_selector
|
|
link_db = [(paring_data[0])]
|
|
while link_db.size > 0
|
|
entry = link_db.shift
|
|
if entry.is_a?(::Hash)
|
|
if entry[:object].is_any?(::Array, ::Hash)
|
|
# get children and assign
|
|
children = children_of.call(entry[:object])
|
|
children.each do |child|
|
|
entry[:partner][(child[:parent_selector])] = child[:partner]
|
|
if child[:partner].is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
|
|
link_db << child
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result = original_partner
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:object => old_root, :credential => credential}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def try_persist(the_object=nil, credential=nil, options={})
|
|
result = nil
|
|
if the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
result = the_object
|
|
else
|
|
begin
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a credential (36 character limit) as a String or Symbol"
|
|
end
|
|
unless options.is_a?(::Hash)
|
|
raise ArgumentError, "You must supply persisting options as a Hash"
|
|
end
|
|
table = ::GxG::Database::Database::element_table_for_instance(the_object)
|
|
if table == :unspecified
|
|
raise ArgumentError, "You provided an object that cannot be persisted."
|
|
end
|
|
if [:element_hash, :element_array].include?(table)
|
|
result = self.iterative_persist(the_object, credential, options)
|
|
else
|
|
result = the_object
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:object => the_object, :credential => credential, :options => options}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def retrieve_by_address(the_address={}, credential=nil, delegate=nil)
|
|
result = nil
|
|
begin
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
if self.alive?
|
|
if self.open?
|
|
address = {}
|
|
if the_address[:database]
|
|
unless the_address[:database] == self
|
|
raise ArgumentError, "Database mismatch. Call this method on database #{the_address[:database].inspect}"
|
|
end
|
|
address[:database] = the_address[:database]
|
|
else
|
|
address[:database] = self
|
|
end
|
|
unless ((the_address[:table]) and (the_address[:dbid].is_a?(::Integer)))
|
|
raise ArgumentError, "Invalid database address: #{the_address.inspect}"
|
|
end
|
|
if the_address[:table].is_a?(::Integer)
|
|
address[:table] = ::GxG::Database::Database::element_table_by_index(the_address[:table])
|
|
else
|
|
address[:table] = the_address[:table]
|
|
end
|
|
address[:dbid] = the_address[:dbid]
|
|
unless self.element_in_trash?(address[:table], address[:dbid])
|
|
if [:persisted_array, :persisted_hash].include?(address[:table])
|
|
the_permission = address[:database].effective_element_permission(address[:table], address[:dbid], credential)
|
|
else
|
|
the_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => true}
|
|
end
|
|
# Review ????
|
|
# puts "Effective Permission: #{the_permission.inspect} for #{address[:table].inspect},#{address[:dbid].inspect}"
|
|
if the_permission[:read] == true
|
|
case address[:table]
|
|
when :element_array
|
|
record = address[:database].connector()[(address[:table].to_s.to_sym)].select(:uuid).where({:dbid => (address[:dbid])}).first
|
|
if record
|
|
result = ::GxG::Database::PersistedArray.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
|
|
else
|
|
raise Exception, "Unable to retrieve PersistedArray"
|
|
end
|
|
when :element_hash
|
|
record = address[:database].connector()[:element_hash].select(:uuid, :format).where({:dbid => (address[:dbid])}).first
|
|
if record
|
|
result = ::GxG::Database::PersistedHash.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
|
|
if result
|
|
if record[:format].to_s.size > 0
|
|
result.set_format(record[:format].to_sym)
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Unable to retrieve PersistedHash"
|
|
end
|
|
else
|
|
raise Exception, "Invalid table specifier: #{address[:table].inspect}"
|
|
end
|
|
else
|
|
# At this point the idea is that if you don't have permissions to read it, you never see it
|
|
# raise Exception, "You don't have permission to retrieve this element"
|
|
end
|
|
#
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:address => (the_address), :credential => (credential), :delegate => (delegate)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def retrieve_by_uuid(uuid=nil, credential=nil, delegate=nil)
|
|
result = nil
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
address = nil
|
|
record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
|
|
else
|
|
record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
|
|
end
|
|
end
|
|
if address
|
|
result = self.retrieve_by_address(address, credential, delegate)
|
|
else
|
|
# ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
|
|
already_checked = [(self)]
|
|
::GxG::DB[:roles].each_pair do |the_db_role, the_database|
|
|
unless already_checked.include?(the_database)
|
|
record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
|
|
result = the_database.retrieve_by_address(address, credential, delegate)
|
|
break
|
|
else
|
|
record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
|
|
if record
|
|
address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
|
|
result = the_database.retrieve_by_address(address, credential, delegate)
|
|
break
|
|
end
|
|
end
|
|
already_checked << the_database
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential), :delegate => (delegate)}})
|
|
end
|
|
result
|
|
end
|
|
# Element Write-Lock Handling methods
|
|
def reserve_element_locks(credential=nil, manifest=[], type=:read)
|
|
result = nil
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a valid credential UUID to reserve locks"
|
|
end
|
|
unless manifest.is_a?(::Array)
|
|
raise ArguementError, "You must supply an Array of tables and record ids to reserve for writing"
|
|
end
|
|
unless [:write, :read].include?(type)
|
|
raise ArgumentError, "You must specify a type of reservation: either :write or :read are supported"
|
|
end
|
|
if manifest.size > 0
|
|
reservation_uuid = ::GxG::uuid_generate()
|
|
while @connector[:element_locks].filter({:reservation => (reservation_uuid.to_s)}).count > 0
|
|
reservation_uuid = ::GxG::uuid_generate()
|
|
end
|
|
if type == :write
|
|
if self.db_permissions()[:write]
|
|
# Write-lock must be unique to the elements
|
|
manifest.each do |entry|
|
|
if @connector[:element_locks].filter({:type => (type.to_s), :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
|
|
self.release_element_locks(reservation_uuid)
|
|
# raise Exception, "A reservation already exists for element #{entry[:table]}:#{entry[:dbid]}"
|
|
# Note - just silently fail and return Nil - no reservation for you.
|
|
break
|
|
else
|
|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
end
|
|
end
|
|
# if write-reservation was made
|
|
if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).count > 0
|
|
result = reservation_uuid.to_sym
|
|
end
|
|
end
|
|
else
|
|
# Read-lock reservation - can share elements
|
|
if self.db_permissions()[:write]
|
|
manifest.each do |entry|
|
|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
#
|
|
end
|
|
end
|
|
# Note - on a read-only db it will act as though it has a read-reservation since no deletions can be made.
|
|
result = reservation_uuid.to_sym
|
|
end
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential), :manifest => (manifest), :type => (type)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def release_element_locks(reservation_uuid=nil)
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(reservation_uuid)
|
|
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to release locks"
|
|
end
|
|
if self.db_permissions()[:write]
|
|
@connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).delete
|
|
end
|
|
# on read-only db, no reservation was made so mission accomplished
|
|
result = true
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def reservation_add_element(reservation_uuid=nil, table=:unspecified, dbid=0)
|
|
result = false
|
|
#
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(reservation_uuid)
|
|
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to add an element to the reserve locks"
|
|
end
|
|
record = @connector[:element_locks].select(:type, :reservation, :credential).where({:reservation => reservation_uuid.to_s}).first
|
|
if record
|
|
type = record[:type].to_sym
|
|
credential = record[:credential].to_sym
|
|
manifest = self.element_manifest(table, dbid, false)
|
|
if type == :write
|
|
if self.db_permissions()[:write]
|
|
#
|
|
manifest.each do |entry|
|
|
if @connector[:element_locks].filter({:type => (type.to_s), :reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count == 0
|
|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
end
|
|
end
|
|
result = true
|
|
end
|
|
else
|
|
# read
|
|
if self.db_permissions()[:write]
|
|
#
|
|
manifest.each do |entry|
|
|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
|
|
end
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def reservation_remove_element(reservation_uuid=nil, table=:unspecified, dbid=0)
|
|
result = false
|
|
#
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(reservation_uuid)
|
|
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to remove element from reserve locks"
|
|
end
|
|
record = @connector[:element_locks].select(:reservation).where({:reservation => reservation_uuid.to_s}).first
|
|
if record
|
|
manifest = self.element_manifest(table, dbid, false)
|
|
if self.db_permissions()[:write]
|
|
#
|
|
manifest.each do |entry|
|
|
if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
|
|
@connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).delete
|
|
end
|
|
end
|
|
result = true
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Database not available (defunct)"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def empty_trash()
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
scrub_list = []
|
|
@connector[:trash].filter({}).each do |entry|
|
|
the_table = entry[:element_table].to_sym
|
|
the_dbid = entry[:elementid]
|
|
if @connector[:element_locks].filter({:type => "write", :element_table => the_table.to_s, :elementid => the_dbid}).count == 0
|
|
# delete full manifest
|
|
manifest = self.element_manifest(the_table, the_dbid, true)
|
|
manifest.each do |item|
|
|
@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
|
|
@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
|
|
end
|
|
scrub_list << entry[:dbid]
|
|
end
|
|
end
|
|
scrub_list.each do |entry|
|
|
@connector[:trash].filter({:dbid => entry}).delete
|
|
end
|
|
if @connector[:trash].filter({}).count == 0
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_in_trash?(table=:unspecified, dbid=0)
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
if @connector[:trash].filter({:element_table => table.to_s, :elementid => dbid}).count > 0
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def element_recover(table=:unspecified, dbid=0)
|
|
# recover an element from the trash
|
|
result = false
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
unless [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
unless dbid > 0
|
|
raise ArgumentError, "dbid must be greater than zero"
|
|
end
|
|
if self.element_in_trash?(table, dbid)
|
|
scrub_list = []
|
|
manifest = self.element_manifest(table, dbid)
|
|
manifest.each do |entry|
|
|
if @connector[:trash].filter({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).count > 0
|
|
record = @connector[:trash].select(:dbid).where({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).first
|
|
if record
|
|
scrub_list << record[:dbid]
|
|
end
|
|
end
|
|
end
|
|
scrub_list.each do |entry|
|
|
@connector[:trash].filter({:dbid => entry}).delete
|
|
end
|
|
result = true
|
|
else
|
|
# it was never in the trash, so mission accomplished
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
|
|
end
|
|
result
|
|
end
|
|
# ### Format handing methods
|
|
def format_template(type=:structure)
|
|
result = nil
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless type.is_any?(::String, ::Symbol)
|
|
raise ArgumentError, "You must supply a type as a String or Symbol: :structure or :binary"
|
|
end
|
|
the_uuid = ::GxG::uuid_generate()
|
|
while (@connector[:formats].filter({:uuid => (the_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (the_uuid)}).count > 0)
|
|
the_uuid = ::GxG::uuid_generate()
|
|
end
|
|
result = {:uuid => (the_uuid.to_sym), :type => (type.to_s), :ufs => "unspecified", :title => "", :version => BigDecimal(0.0), :mime_types => [], :content => nil}
|
|
case type
|
|
when :structure
|
|
result[:content] = {}
|
|
when :binary
|
|
result[:content] = {:sample_size => 0, :recognition => "", :import => "", :export => ""}
|
|
else
|
|
raise Exception, "Invalid format type specified: #{type.inspect}. Use :structure or :binary"
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
result = nil
|
|
log_error({:error => the_error, :parameters => {:type => (type)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def format_list(search_options={})
|
|
result = []
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless search_options.is_a?(::Hash)
|
|
raise ArgumentError, "You must specify search options as a Hash"
|
|
end
|
|
the_filter = {}
|
|
if search_options[:uuid]
|
|
the_filter[:uuid] = search_options[:uuid].to_s
|
|
end
|
|
if search_options[:type]
|
|
the_filter[:type] = search_options[:type].to_s
|
|
end
|
|
if search_options[:ufs]
|
|
the_filter[:ufs] = search_options[:ufs].to_s[0..4096]
|
|
end
|
|
if search_options[:title]
|
|
the_filter[:title] = search_options[:title].to_s[0..256]
|
|
end
|
|
if search_options[:version].is_a?(::Numeric)
|
|
the_filter[:version] = ::BigDecimal.new(search_options[:version].to_s)
|
|
end
|
|
mime_type = search_options[:mime_type].to_s
|
|
if mime_type.size > 0
|
|
@connector[:formats].filter(the_filter).where(::Sequel.like(:mime_types, ("%" + mime_type + "%"))).order(::Sequel[:version].desc).each do |entry|
|
|
result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
|
|
end
|
|
else
|
|
@connector[:formats].filter(the_filter).order(::Sequel[:version].desc).each do |entry|
|
|
result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def format_load(search_options={})
|
|
result = nil
|
|
begin
|
|
if self.alive?()
|
|
if self.open?
|
|
format_stub = self.format_list(search_options).first
|
|
if format_stub
|
|
format_record = @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).first
|
|
if format_record
|
|
content = ""
|
|
@connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).order(:ordinal).each do |entry|
|
|
content << entry[:content]
|
|
end
|
|
format_record.delete(:dbid)
|
|
format_record[:ufs] = format_stub[:ufs]
|
|
format_record[:type] = format_stub[:type]
|
|
if content.size > 0
|
|
if content.base64?
|
|
content = content.decode64
|
|
end
|
|
if content.json?
|
|
content = ::JSON::parse(content.to_s, {:symbolize_names => true})
|
|
end
|
|
if content[:type]
|
|
content = ::Hash.gxg_import(content)
|
|
end
|
|
format_record[:content] = content
|
|
else
|
|
format_record[:content] = nil
|
|
end
|
|
#
|
|
if format_record[:mime_types].size > 0
|
|
if format_record[:mime_types].include?(",")
|
|
format_record[:mime_types] = format_record[:mime_types].split(",")
|
|
else
|
|
format_record[:mime_types] = [(format_record[:mime_types])]
|
|
end
|
|
end
|
|
#
|
|
result = format_record
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def new_structure_from_format(credential=nil, search_options={})
|
|
result = nil
|
|
begin
|
|
if self.alive?()
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
format = self.format_load(search_options)
|
|
if format
|
|
# TODO: add support for :binary formats.
|
|
unless format[:type] == :structure
|
|
raise Exception, "Only formats of type :structure are supported"
|
|
end
|
|
result = self.try_persist(format[:content], credential)
|
|
if result.is_a?(::GxG::Database::PersistedHash)
|
|
result.set_format(format[:uuid].to_sym)
|
|
@connector[:element_hash].filter({:uuid => result.uuid.to_s}).update({:format => format[:uuid].to_s})
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def format_create(format_record=nil)
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Unable to write to database"
|
|
end
|
|
if format_record.is_a?(::Hash)
|
|
unless ::GxG::valid_uuid?(format_record[:uuid])
|
|
raise Exception, "Invalid UUID passed for the format"
|
|
end
|
|
unless format_record[:type].is_any?([::String, ::Symbol])
|
|
raise Exception, "You must pass the format_record[:type] as a String or Symbol"
|
|
end
|
|
unless format_record[:ufs].is_any?([::String, ::Symbol])
|
|
raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
|
|
end
|
|
unless format_record[:title].is_a?(::String)
|
|
raise Exception, "You must pass the format_record[:title] as a String"
|
|
end
|
|
unless format_record[:version].is_a?(::Numeric)
|
|
raise Exception, "You must pass the format_record[:version] as a Numeric"
|
|
end
|
|
unless format_record[:mime_types].is_any?(::String, ::Array)
|
|
raise Exception, "You must pass the format_record[:mime_types] as a String"
|
|
end
|
|
unless format_record[:content].is_a?(::Hash)
|
|
raise Exception, "You must pass the format_record[:content] as a Hash"
|
|
end
|
|
unless self.persistable?(format_record[:content])
|
|
raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
|
|
end
|
|
if (@connector[:settings].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:element_text].filter({:uuid => format_record[:uuid].to_s}).count == 0)
|
|
new_record = {:uuid => (format_record[:uuid].to_s)}
|
|
new_record[:type] = format_record[:type].to_s
|
|
new_record[:ufs] = format_record[:ufs].to_s
|
|
new_record[:title] = format_record[:title].to_s[0..256]
|
|
new_record[:version] = ::BigDecimal.new(format_record[:version].to_s)
|
|
if format_record[:mime_types].is_a?(::Array)
|
|
the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
|
|
else
|
|
the_mimes = format_record[:mime_types].to_s[0..4096]
|
|
end
|
|
new_record[:mime_types] = the_mimes
|
|
content = format_record[:content].gxg_export.to_json.encode64
|
|
new_dbid = @connector[:formats].insert(new_record)
|
|
if content.size > 0
|
|
::GxG::apportioned_ranges(content.size, 4096).each_with_index do |portion, ordinal|
|
|
@connector[:text_page].insert({:parent_uuid => (new_record[:uuid].to_s), :ordinal => (ordinal), :length => (content[(portion)].size), :content => (content[(portion)])})
|
|
end
|
|
end
|
|
if new_dbid
|
|
result = true
|
|
end
|
|
#
|
|
if result == true && ::GxG::DB[:roles][:formats] == self
|
|
already = [(self)]
|
|
GxG::DB[:roles].values.each do |the_db|
|
|
unless already.include?(the_db)
|
|
the_db.format_create(format_record)
|
|
already << the_db
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "That format UUID is already in use, please generate another"
|
|
end
|
|
#
|
|
else
|
|
raise ArgumentError, "You must supply a valid format record as a Hash"
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def format_update(format_record=nil)
|
|
format_record = format_record.clone
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Unable to write to database"
|
|
end
|
|
if format_record.is_a?(::Hash)
|
|
unless ::GxG::valid_uuid?(format_record[:uuid])
|
|
raise Exception, "Invalid UUID passed for the format"
|
|
end
|
|
record = @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).first
|
|
if record
|
|
format_update = {}
|
|
record.keys.each do |the_key|
|
|
if [:type, :ufs, :title, :version, :mime_types].include?(the_key)
|
|
case the_key
|
|
when :type
|
|
unless format_record[:type].is_any?(::String, ::Symbol)
|
|
raise Exception, "You must pass the format_record[:type] as a String or Symbol"
|
|
end
|
|
when :ufs
|
|
unless format_record[:ufs].is_any?(::String, ::Symbol)
|
|
raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
|
|
end
|
|
when :title
|
|
unless format_record[:title].is_a?(::String)
|
|
raise Exception, "You must pass the format_record[:title] as a String"
|
|
end
|
|
when :version
|
|
unless format_record[:version].is_a?(::Numeric)
|
|
raise Exception, "You must pass the format_record[:version] as a Numeric"
|
|
end
|
|
when :mime_types
|
|
unless format_record[:mime_types].is_any?(::String, ::Array)
|
|
raise Exception, "You must pass the format_record[:mime_types] as a String"
|
|
end
|
|
end
|
|
#
|
|
if the_key == :mime_types
|
|
if format_record[:mime_types].is_a?(::Array)
|
|
the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
|
|
else
|
|
the_mimes = format_record[:mime_types].to_s[0..4096]
|
|
end
|
|
format_record[:mime_types] = the_mimes
|
|
end
|
|
#
|
|
if format_record[(the_key)] != record[(the_key)]
|
|
format_update[(the_key)] = format_record[(the_key)]
|
|
end
|
|
end
|
|
end
|
|
if format_update.keys.size > 0
|
|
# FIXME: :type and :ufs are being passed as a Symbols, but DB requires them to be a strings:
|
|
if format_update[:type].is_a?(::Symbol)
|
|
format_update[:type] = format_update[:type].to_s
|
|
end
|
|
if format_update[:ufs].is_a?(::Symbol)
|
|
format_update[:ufs] = format_update[:ufs].to_s
|
|
end
|
|
@connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update(format_update)
|
|
end
|
|
if format_record[:content].is_a?(::Hash)
|
|
unless self.persistable?(format_record[:content])
|
|
raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
|
|
end
|
|
old_segments = []
|
|
content = format_record[:content].gxg_export.to_json.encode64
|
|
@connector[:text_page].select(:dbid).where({:parent_uuid => format_record[:uuid].to_s}).order(:ordinal).each do |entry|
|
|
old_segments << entry[:dbid]
|
|
end
|
|
if content.size == 0
|
|
if old_segments.size > 0
|
|
@connector[:text_page].filter({:parent_uuid => format_record[:uuid].to_s}).delete
|
|
end
|
|
else
|
|
new_segments = ::GxG::apportioned_ranges(content.size, 4096)
|
|
if old_segments.size > new_segments.size
|
|
the_count = old_segments.size
|
|
else
|
|
the_count = new_segments.size
|
|
end
|
|
manifest = []
|
|
(0..(the_count - 1)).each do |the_ordinal|
|
|
if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
|
|
#overwrite with new segment range
|
|
manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
|
|
else
|
|
if new_segments[(the_ordinal)]
|
|
# create new segment at this ordinal
|
|
manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
|
|
else
|
|
# delete old segment
|
|
manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
|
|
end
|
|
end
|
|
end
|
|
manifest.each do |action|
|
|
case action[:operation]
|
|
when :overwrite
|
|
@connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
|
|
when :create
|
|
@connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
|
|
when :delete
|
|
@connector[:text_page].filter({:dbid => (action[:dbid])}).delete
|
|
end
|
|
end
|
|
@connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update({:version => (::BigDecimal.new(format_record[:version].to_s) + 0.0001)})
|
|
#
|
|
end
|
|
end
|
|
#
|
|
result = true
|
|
#
|
|
if result == true && ::GxG::DB[:roles][:formats] == self
|
|
already = [(self)]
|
|
GxG::DB[:roles].values.each do |the_db|
|
|
unless already.include?(the_db)
|
|
the_db.format_update(format_record)
|
|
already << the_db
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
result = self.format_create(format_record)
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply a valid format record as a Hash"
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def format_destroy(search_options={})
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Unable to write to database"
|
|
end
|
|
format_stub = self.format_list(search_options).first
|
|
if format_stub
|
|
@connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).delete
|
|
if @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).count > 0
|
|
@connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).delete
|
|
end
|
|
result = true
|
|
#
|
|
if result == true && ::GxG::DB[:roles][:formats] == self
|
|
already = [(self)]
|
|
GxG::DB[:roles].values.each do |the_db|
|
|
unless already.include?(the_db)
|
|
the_db.format_destroy(search_options)
|
|
already << the_db
|
|
end
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
|
|
end
|
|
result
|
|
end
|
|
# Settings methods
|
|
def setting_keys()
|
|
result = []
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
@connector[:settings].all.each do |the_entry|
|
|
result << the_entry[:title].to_sym
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def [](setting_key=nil)
|
|
result = nil
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
record = @connector[:settings].filter({:title => setting_key.to_s}).first
|
|
if record
|
|
content = ""
|
|
@connector[:text_page].select(:content).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
|
|
content << entry[:content]
|
|
end
|
|
if content.size > 0
|
|
result = content.unserialize()
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def []=(setting_key=nil, object=nil)
|
|
result = object
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Unable to write to database"
|
|
end
|
|
unless setting_key.is_a?(::Symbol)
|
|
raise ArgumentError, "You must supply a setting key as a Symbol"
|
|
end
|
|
unless setting_key.to_s.size > 0
|
|
raise ArgumentError, "You must supply a setting key whose length is greater than zero"
|
|
end
|
|
record = @connector[:settings].filter({:title => setting_key.to_s}).first
|
|
unless record
|
|
new_uuid = ::GxG::uuid_generate().to_s
|
|
while (@connector[:settings].filter({:uuid => (new_uuid)}).count > 0 or @connector[:formats].filter({:uuid => (new_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (new_uuid)}).count > 0)
|
|
new_uuid = ::GxG::uuid_generate().to_s
|
|
end
|
|
@connector[:settings].insert({:title => setting_key.to_s, :uuid => new_uuid.to_s})
|
|
record = @connector[:settings].filter({:uuid => (new_uuid)}).first
|
|
end
|
|
if record
|
|
@connector[:settings].filter({:dbid => (record[:dbid])}).update({:version => (record[:version] + 0.0001)})
|
|
content = object.serialize()
|
|
old_segments = []
|
|
@connector[:text_page].select(:dbid).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
|
|
old_segments << entry[:dbid]
|
|
end
|
|
if content.size == 0
|
|
if old_segments.size > 0
|
|
@connector[:text_page].filter({:parent_uuid => record[:uuid].to_s}).delete
|
|
end
|
|
else
|
|
new_segments = ::GxG::apportioned_ranges(content.size, 4096)
|
|
if old_segments.size > new_segments.size
|
|
the_count = old_segments.size
|
|
else
|
|
the_count = new_segments.size
|
|
end
|
|
manifest = []
|
|
(0..(the_count - 1)).each do |the_ordinal|
|
|
if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
|
|
#overwrite with new segment range
|
|
manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
|
|
else
|
|
if new_segments[(the_ordinal)]
|
|
# create new segment at this ordinal
|
|
manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
|
|
else
|
|
# delete old segment
|
|
manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
|
|
end
|
|
end
|
|
end
|
|
manifest.each do |action|
|
|
case action[:operation]
|
|
when :overwrite
|
|
@connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
|
|
when :create
|
|
@connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
|
|
when :delete
|
|
@connector[:text_page].filter({:dbid => (action[:dbid])}).delete
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
raise Exception, "Internal Error - could not retrieve setting record: #{setting_key.inspect}"
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:setting => setting_key, :object => object}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
# Permission policy support:
|
|
def permission_policy()
|
|
result = nil
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
if @authority
|
|
result = @authority.permission_policy()
|
|
else
|
|
result = @permission_policy
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def permission_policy_load()
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
if @authority
|
|
result = @authority.permission_policy_load()
|
|
else
|
|
@permission_policy = (self[:permission_policy] || [])
|
|
result = true
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def permission_policy_save()
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
if @authority
|
|
result = @authority.permission_policy_save()
|
|
else
|
|
self[:permission_policy] = (@permission_policy || [])
|
|
result = true
|
|
end
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def enforce_permission_policy(event={})
|
|
# Event format: enforce_permission_policy({:action => :extend, :credential => nil, :source => nil, :destination => nil})
|
|
# Rule format: {:event => :create, :credential => :any, :action => {:extend_permissions => nil}}
|
|
the_policy = self.permission_policy()
|
|
result = false
|
|
begin
|
|
if self.alive?()
|
|
if self.open?()
|
|
actions = []
|
|
the_policy.each do |rule|
|
|
if event[:action] == rule[:event]
|
|
if rule[:credential] == event[:credential] || rule[:credential] == :any
|
|
actions << rule[:action]
|
|
end
|
|
end
|
|
end
|
|
actions.each do |action|
|
|
case action.keys[0]
|
|
when :extend_permissions
|
|
if action[:use_permission].is_a?(::Hash)
|
|
use_permission = action[:use_permission]
|
|
else
|
|
use_permission = nil
|
|
end
|
|
credential = event[:credential]
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You MUST provide a valid UUID as :credential."
|
|
end
|
|
new_credential = action[:extend_permissions]
|
|
source = event[:source]
|
|
unless source.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
raise ArgumentError, "You MUST provide a persisted object as :source."
|
|
end
|
|
unless source.db_address
|
|
raise Exception, "You provided a Deactivated object as :source."
|
|
end
|
|
destination = event[:destination]
|
|
unless destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
raise ArgumentError, "You MUST provide a persisted object as :source."
|
|
end
|
|
unless destination.db_address
|
|
raise Exception, "You provided a Deactivated object as :destination."
|
|
end
|
|
#
|
|
permissions = self.element_permissions(source.db_address()[:table],source.db_address()[:dbid])
|
|
permission_db = []
|
|
if [:persisted_array, :persisted_hash].include?(destination.db_address()[:table])
|
|
search_db = [{:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}]
|
|
permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
|
|
while search_db.size > 0 do
|
|
entry = search_db.shift
|
|
self.element_manifest(entry[:table],entry[:dbid],false).each do |item|
|
|
if [:persisted_array, :persisted_hash].include?(item[:table])
|
|
search_db << item
|
|
permission_db << item
|
|
end
|
|
end
|
|
end
|
|
permission_db.each do |the_reference|
|
|
permissions.each do |record|
|
|
self.assign_element_permission(the_reference[:table],the_reference[:dbid], credential, record[:permissions])
|
|
if use_permission
|
|
self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, use_permission)
|
|
else
|
|
self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, record[:permissions])
|
|
end
|
|
end
|
|
end
|
|
# destination.search do |the_value, the_selector, the_container|
|
|
# if [:persisted_array, :persisted_hash].include?(the_value.db_address()[:table])
|
|
# permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
|
|
# end
|
|
# end
|
|
#
|
|
end
|
|
# permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
|
|
# if destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
|
|
# destination.search do |the_value, the_selector, the_container|
|
|
# permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
|
|
# end
|
|
# end
|
|
# permission_db.each do |item|
|
|
# self.element_manifest(item[:table],item[:dbid],false).each do |entry|
|
|
# permissions.each do |record|
|
|
# self.assign_element_permission(entry[:table],entry[:dbid], credential, record[:permissions])
|
|
# if use_permission
|
|
# self.assign_element_permission(entry[:table],entry[:dbid], new_credential, use_permission)
|
|
# else
|
|
# self.assign_element_permission(entry[:table],entry[:dbid], new_credential, record[:permissions])
|
|
# end
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
end
|
|
end
|
|
result = true
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:event => event}})
|
|
end
|
|
result
|
|
end
|
|
# ### Import methods (deprecated in favor of sychronize_records)
|
|
def import(credential=nil, the_record=nil)
|
|
#
|
|
result = nil
|
|
if self.alive?
|
|
if self.open?
|
|
if self.db_permissions()[:write]
|
|
cleanup_manifest = []
|
|
begin
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
if the_record.is_a?(::Hash)
|
|
case the_record[:type]
|
|
when :element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime
|
|
new_dbid = @connector[(the_record[:type])].insert({:version => (the_record[:version]), :content => (the_record[:content])})
|
|
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
result = {:table => (the_record[:table]), :dbid => (new_dbid)}
|
|
when :element_text
|
|
the_uuid = ::GxG::uuid_generate()
|
|
while (@connector[:element_text].filter({:uuid => the_uuid}).count > 0)
|
|
the_uuid = ::GxG::uuid_generate()
|
|
end
|
|
new_dbid = @connector[:element_text].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
|
|
cleanup_manifest << {:table => :element_text, :dbid => (new_dbid)}
|
|
::GxG::apportioned_ranges(the_record[:content].size, 4096).each_with_index do |the_range,the_ordinal|
|
|
@connector[:text_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content][(the_range)].size), :content => (the_record[:content][(the_range)])})
|
|
end
|
|
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
result = {:table => :element_text, :dbid => (new_dbid)}
|
|
when :element_binary
|
|
the_uuid = ::GxG::uuid_generate()
|
|
while (@connector[:element_binary].filter({:uuid => the_uuid}).count > 0)
|
|
the_uuid = ::GxG::uuid_generate()
|
|
end
|
|
new_dbid = @connector[:element_binary].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
|
|
cleanup_manifest << {:table => :element_binary, :dbid => (new_dbid)}
|
|
::GxG::apportioned_ranges(the_record[:content].size, 65536).each_with_index do |the_range,the_ordinal|
|
|
@connector[:binary_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content].data()[(the_range)].size), :content => (the_record[:content].data()[(the_range)])})
|
|
end
|
|
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
result = {:table => :element_binary, :dbid => (new_dbid)}
|
|
when :element_array
|
|
# {:type => :element_array, :uuid => @uuid, :title => @title, :version => @version, :content => []}
|
|
existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
|
|
if existing
|
|
if the_record[:version] > existing[:version]
|
|
unless self.element_destroy(credential, :element_array, existing[:dbid])
|
|
raise Exception, "Error replacing array element - Aborting"
|
|
end
|
|
existing = nil
|
|
else
|
|
log_warn("Import record is older than existing one - Skipping")
|
|
new_dbid = existing[:dbid]
|
|
end
|
|
end
|
|
unless existing
|
|
new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version])})
|
|
cleanup_manifest << {:table => :element_array, :dbid => (new_dbid)}
|
|
self.assign_element_permission(:element_array, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
the_record[:content].each_with_index do |element, indexer|
|
|
new_address = self.import(credential, (element))
|
|
if new_address
|
|
cleanup_manifest << new_address
|
|
@connector[:array_links].insert({:parent_uuid => (the_record[:uuid].to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
|
|
else
|
|
raise Exception, "Error processing the import record - Aborting"
|
|
end
|
|
end
|
|
end
|
|
result = {:table => :element_array, :dbid => (new_dbid)}
|
|
when :element_hash
|
|
# {:type => :element_hash, :uuid => @uuid, :title => @title, :version => @version, :format => @format, :content => {}}
|
|
existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
|
|
if existing
|
|
if the_record[:version] > existing[:version]
|
|
unless self.element_destroy(credential, :element_hash, existing[:dbid])
|
|
raise Exception, "Error replacing hash element - Aborting"
|
|
end
|
|
existing = nil
|
|
else
|
|
log_warn("Import record is older than existing one - Skipping")
|
|
new_dbid = existing[:dbid]
|
|
end
|
|
end
|
|
unless existing
|
|
new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version]), :format => (the_record[:format].to_s)})
|
|
cleanup_manifest << {:table => :element_hash, :dbid => (new_dbid)}
|
|
self.assign_element_permission(:element_hash, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
the_record[:content].keys.each_with_index do |the_key, indexer|
|
|
new_address = self.import(credential, (the_record[:content][(the_key)]))
|
|
if new_address
|
|
cleanup_manifest << new_address
|
|
@connector[:hash_links].insert({:parent_uuid => (the_record[:uuid].to_s), :property => (the_key.to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
|
|
else
|
|
raise Exception, "Error processing the import record - Aborting"
|
|
end
|
|
end
|
|
end
|
|
result = {:table => :element_hash, :dbid => (new_dbid)}
|
|
else
|
|
raise ArgumentError, "Invalid table specified"
|
|
end
|
|
else
|
|
raise ArgumentError, "You must supply an import record as a Hash"
|
|
end
|
|
rescue Exception => the_error
|
|
# clean up
|
|
cleanup_manifest.each do |entry|
|
|
self.element_destroy(credential, entry[:table], entry[:dbid])
|
|
end
|
|
log_error({:error => the_error, :parameters => {:the_record => (the_record)}})
|
|
end
|
|
else
|
|
log_warn("Cannot import records into a read-only Database - Aborting")
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
# ### Search function method
|
|
def uuid_list(credential=nil, criteria={})
|
|
# will return an array of all structures the credential has (at least) read access to
|
|
result = []
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
|
|
end
|
|
unless criteria.is_a?(::Hash)
|
|
raise ArgumentError, "You must supply a search criteria as a Hash"
|
|
end
|
|
hash_criteria = {}
|
|
array_criteria = {}
|
|
# Limit, Offset, and Page selection
|
|
if criteria[:limit].is_a?(::Integer)
|
|
limit = criteria[:limit]
|
|
else
|
|
limit = nil
|
|
end
|
|
if criteria[:offset].is_a?(::Integer)
|
|
offset = criteria[:offset]
|
|
else
|
|
offset = 0
|
|
end
|
|
if criteria[:page].is_a?(::Integer)
|
|
page = criteria[:page]
|
|
unless limit
|
|
limit = 100
|
|
end
|
|
offset = ((page * limit) - 1)
|
|
else
|
|
page = nil
|
|
end
|
|
# UUID, Title, and Format searches
|
|
if ::GxG::valid_uuid?(criteria[:uuid])
|
|
hash_criteria[:uuid] = criteria[:uuid].to_s
|
|
array_criteria[:uuid] = criteria[:uuid].to_s
|
|
end
|
|
if criteria[:title].is_a?(::String)
|
|
hash_criteria[:title] = criteria[:title].to_s[0..256]
|
|
array_criteria[:title] = criteria[:title].to_s[0..256]
|
|
end
|
|
if criteria[:format].is_any?([::String, ::Symbol])
|
|
if ::GxG::valid_uuid?(criteria[:format])
|
|
format_list = self.format_list({:uuid => criteria[:format].to_sym})
|
|
unless format_list.size > 0
|
|
raise ArgumentError, "Format not found: #{criteria[:format].inspect}"
|
|
end
|
|
hash_criteria[:format] = criteria[:format].to_s
|
|
end
|
|
end
|
|
if criteria[:ufs].is_any?(::String, ::Symbol)
|
|
if criteria[:ufs_version].is_a?(::Numeric)
|
|
format_list = self.format_list({:ufs => criteria[:ufs].to_sym, :version => ::BigDecimal.new(criteria[:ufs_version].to_s)})
|
|
else
|
|
format_list = self.format_list({:ufs => criteria[:ufs].to_sym})
|
|
end
|
|
unless format_list.size > 0
|
|
if criteria[:ufs_version].is_a?(::Float)
|
|
raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}, Version: #{criteria[:ufs_version].inspect}"
|
|
else
|
|
raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}"
|
|
end
|
|
end
|
|
hash_criteria[:format] = format_list[0][:uuid].to_s
|
|
end
|
|
# Output column selection: (very very basic at first)
|
|
selectables = [:uuid, :title, :version]
|
|
selected = []
|
|
if criteria[:select].is_any?(::Symbol, ::Array)
|
|
if criteria[:select].is_a?(::Array)
|
|
criteria[:select].each do |the_column_symbol|
|
|
if selectables.include?(the_column_symbol)
|
|
selected << the_column_symbol
|
|
end
|
|
end
|
|
else
|
|
if selectables.include?(criteria[:select])
|
|
selected << criteria[:select]
|
|
end
|
|
end
|
|
end
|
|
if selected.size == 0
|
|
selected << :uuid
|
|
end
|
|
#
|
|
reduce_to_datetime = Proc.new do |the_value|
|
|
the_result = nil
|
|
db_content = nil
|
|
if the_value.is_a?(::String)
|
|
if the_value.include?("between")
|
|
the_times = the_value.split("between")[1].split("and")
|
|
the_times[0] = ::Chronic.parse(the_times[0])
|
|
the_times[1] = ::Chronic.parse(the_times[1])
|
|
if the_times[0].is_a?(::Time) && the_times[1].is_a?(::Time)
|
|
db_content = ((the_times[0].to_datetime)..(the_times[1].to_datetime))
|
|
else
|
|
raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
|
|
end
|
|
else
|
|
if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
|
|
db_content = ::DateTime::parse(the_value)
|
|
else
|
|
the_times = ::Chronic.parse(the_value)
|
|
if the_times.is_a?(::Time)
|
|
db_content = the_times.to_datetime
|
|
else
|
|
raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if the_value.is_a?(::Range)
|
|
the_times = [(the_value.first), (the_value.last)]
|
|
if the_times[0].is_a?(::Time)
|
|
the_times[0] = the_times[0].to_datetime
|
|
end
|
|
if the_times[0].is_a?(::Date)
|
|
the_times[0] = ::DateTime::parse(the_times[0].to_s)
|
|
end
|
|
if the_times[1].is_a?(::Time)
|
|
the_times[1] = the_times[1].to_datetime
|
|
end
|
|
if the_times[1].is_a?(::Date)
|
|
the_times[1] = ::DateTime::parse(the_times[1].to_s)
|
|
end
|
|
the_times = ((the_times[0])..(the_times[1]))
|
|
if the_times.first.is_a?(::DateTime) && the_value.last.is_a?(::DateTime)
|
|
db_content = the_times
|
|
else
|
|
raise Exception, "Invalid Range of dates, use DateTime objects in the Range: #{the_value.inspect}"
|
|
end
|
|
end
|
|
if the_value.is_any?(::Time, ::Date)
|
|
db_content = ::DateTime::parse(the_value.to_s)
|
|
end
|
|
if the_value.is_a?(::DateTime)
|
|
db_content = the_value
|
|
end
|
|
if db_content
|
|
result = db_content
|
|
else
|
|
raise Exception, "Unable to reduce to a DateTime with this input: #{the_value.inspect}"
|
|
end
|
|
the_result
|
|
end
|
|
#
|
|
db_content_value = Proc.new do |the_value|
|
|
the_result = nil
|
|
the_table = ::GxG::Database::Database::element_table_for_instance(the_value)
|
|
if the_table == :unspecified
|
|
raise Exception, "Invalid value passed as persistable: #{the_value.inspect}"
|
|
else
|
|
db_content = nil
|
|
case the_table
|
|
when :element_boolean
|
|
if the_value.is_a?(::TrueClass)
|
|
db_content = 1
|
|
else
|
|
if the_value.is_a?(::FalseClass)
|
|
db_content = 0
|
|
else
|
|
db_content = -1
|
|
end
|
|
end
|
|
when :element_binary
|
|
db_content = ""
|
|
the_value.each do |the_byte|
|
|
db_content << the_byte.to_s(base=16)
|
|
end
|
|
when :element_text
|
|
if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
|
|
db_content = reduce_to_datetime.call(the_value)
|
|
else
|
|
db_content = the_value
|
|
end
|
|
else
|
|
db_content = the_value
|
|
end
|
|
if db_content
|
|
the_result = db_content
|
|
else
|
|
raise Exception, "Could not cast the value to something useful: #{the_value.inspect}"
|
|
end
|
|
end
|
|
the_result
|
|
end
|
|
#
|
|
find_property_records = Proc.new do |a_selector, a_container|
|
|
a_result = []
|
|
a_container.each do |item|
|
|
if item[(a_selector)]
|
|
a_result << item
|
|
end
|
|
end
|
|
a_result
|
|
end
|
|
find_element_records = Proc.new do |a_selector, a_container|
|
|
a_result = []
|
|
a_container.each do |item|
|
|
if item[(a_selector)]
|
|
a_result << item
|
|
end
|
|
end
|
|
a_result
|
|
end
|
|
found_list = []
|
|
# criteria[:property] :
|
|
# For all
|
|
# {:property_name => {:equals => value}}
|
|
# For :element_integer, :element_float, :element_bigdecimal
|
|
# {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
|
|
# For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
|
|
# {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
|
|
# For Text, Binary
|
|
# {:property_name => {:include => value}}
|
|
#
|
|
# criteria[:element] :
|
|
# similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
|
|
#
|
|
if criteria[:property] || criteria[:element]
|
|
# Expansion
|
|
# Text Search: @connector[:text_page].where(::Sequel.like(:content,'%#{search_text}%'))
|
|
# Binary Search: @connector[:binary_page].where(::Sequel.like(:content,'%#{byte_sequence}%'))
|
|
# self.element_parent_address(the_table=:unspecified, the_dbid=0)
|
|
if criteria[:property].is_a?(::Hash)
|
|
# Only ONE property key can be compared (TODO: expand to multiple keys)
|
|
property_matches = []
|
|
last_key = nil
|
|
#
|
|
criteria[:property].keys.each do |property_key|
|
|
# compare each key's value to qualify the parent object:
|
|
# {:html => {:include => "\n"}}
|
|
comparison_op = criteria[:property][(property_key)].keys[0].to_sym
|
|
if criteria[:property][(property_key)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
|
|
comparison_value = reduce_to_datetime.call(criteria[:property][(property_key)][(comparison_op)])
|
|
table_test = ::GxG::Database::Database::element_table_for_instance(comparison_value)
|
|
else
|
|
table_test = ::GxG::Database::Database::element_table_for_instance(criteria[:property][(property_key)][(comparison_op)])
|
|
comparison_value = db_content_value.call(criteria[:property][(property_key)][(comparison_op)])
|
|
end
|
|
#
|
|
if [:element_hash, :element_array].include?(table_test)
|
|
# Only properties with base elements can be compared - not structures (TODO: expand this)
|
|
log_warn("Attempted to compare a structure as a property value (not supported, ignoring).")
|
|
next
|
|
end
|
|
# Review : rewrite - adapt to new arch.
|
|
match_uuid_list = []
|
|
# comparison_value
|
|
@connector[:hash_properties].filter({:property => property_key.to_s}).each do |entry|
|
|
element_value = nil
|
|
case entry[:element]
|
|
when "element_boolean"
|
|
case entry[:element_boolean]
|
|
when -1
|
|
element_value = nil
|
|
when 0
|
|
element_value = false
|
|
when 1
|
|
element_value = nil
|
|
end
|
|
when "element_integer"
|
|
element_value = entry[:element_integer]
|
|
when "element_float"
|
|
element_value = entry[:element_float]
|
|
when "element_bigdecimal"
|
|
element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
|
|
when "element_datetime"
|
|
element_value = entry[:element_datetime]
|
|
when "element_text"
|
|
if entry[:element_text_uuid].to_s.size > 0
|
|
# Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
|
|
element_value = ""
|
|
@connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
|
|
element_value << text_page[:content]
|
|
end
|
|
else
|
|
element_value = entry[:element_text]
|
|
end
|
|
when "element_binary"
|
|
element_value = ::GxG::ByteArray.new
|
|
@connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
|
|
element_value << binary_page[:content]
|
|
end
|
|
end
|
|
#
|
|
begin
|
|
case comparison_op
|
|
when :equals, :at
|
|
if element_value == comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :greater_than, :after
|
|
if element_value > comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :greater_or_equal, :at_or_after
|
|
if element_value >= comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :less_than, :before
|
|
if element_value < comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :less_or_equal, :at_or_before
|
|
if element_value <= comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :between
|
|
if comparison_value.include?(element_value)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :include
|
|
if element_value.to_s.include?(comparison_value.to_s)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :match
|
|
if element_value.to_s.match(comparison_value)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
end
|
|
rescue Exception => cast_error
|
|
# Probably a type casting error (comparison of dissimilar types)
|
|
next
|
|
end
|
|
end
|
|
#
|
|
match_uuid_list.each do |the_matched_uuid|
|
|
# Add to main found list
|
|
parent_record = @connector[:element_hash].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
|
|
if parent_record
|
|
new_record = {}
|
|
selected.each do |the_selected_key|
|
|
if parent_record.keys.include?(the_selected_key)
|
|
new_record[(the_selected_key)] = parent_record[(the_selected_key)]
|
|
if ::GxG::valid_uuid?(new_record[(the_selected_key)])
|
|
new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
|
|
end
|
|
end
|
|
end
|
|
unless found_list.include?(new_record)
|
|
found_list << new_record
|
|
end
|
|
end
|
|
end
|
|
#
|
|
if last_key != property_key
|
|
last_key = property_key
|
|
end
|
|
#
|
|
end
|
|
#
|
|
else
|
|
if criteria[:element].is_a?(::Array)
|
|
criteria[:element].each_with_index do |the_element, element_index|
|
|
comparison_op = criteria[:element][(element_index)].keys[0].to_sym
|
|
if criteria[:element][(element_index)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
|
|
comparison_value = reduce_to_datetime.call(criteria[:element][(element_index)][(comparison_op)])
|
|
element_table = ::GxG::Database::Database::element_table_for_instance(comparison_value)
|
|
else
|
|
element_table = ::GxG::Database::Database::element_table_for_instance(criteria[:element][(element_index)][(comparison_op)])
|
|
comparison_value = db_content_value.call(criteria[:element][(element_index)][(comparison_op)])
|
|
end
|
|
# Structural comparison not supported.
|
|
if [:element_hash, :element_array].include?(element_table)
|
|
log_warn("Attempted to compare a structure as an element (not supported, ignoring).")
|
|
next
|
|
end
|
|
match_uuid_list = []
|
|
# comparison_value
|
|
@connector[:array_elements].filter({:element => element_table.to_s}).each do |entry|
|
|
element_value = nil
|
|
case entry[:element]
|
|
when "element_boolean"
|
|
case entry[:element_boolean]
|
|
when -1
|
|
element_value = nil
|
|
when 0
|
|
element_value = false
|
|
when 1
|
|
element_value = nil
|
|
end
|
|
when "element_integer"
|
|
element_value = entry[:element_integer]
|
|
when "element_float"
|
|
element_value = entry[:element_float]
|
|
when "element_bigdecimal"
|
|
element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
|
|
when "element_datetime"
|
|
element_value = entry[:element_datetime]
|
|
when "element_text"
|
|
if entry[:element_text_uuid].to_s.size > 0
|
|
# Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
|
|
element_value = ""
|
|
@connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
|
|
element_value << text_page[:content]
|
|
end
|
|
else
|
|
element_value = entry[:element_text]
|
|
end
|
|
when "element_binary"
|
|
element_value = ::GxG::ByteArray.new
|
|
@connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
|
|
element_value << binary_page[:content]
|
|
end
|
|
end
|
|
#
|
|
begin
|
|
case comparison_op
|
|
when :equals, :at
|
|
if element_value == comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :greater_than, :after
|
|
if element_value > comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :greater_or_equal, :at_or_after
|
|
if element_value >= comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :less_than, :before
|
|
if element_value < comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :less_or_equal, :at_or_before
|
|
if element_value <= comparison_value
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :between
|
|
if comparison_value.include?(element_value)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :include
|
|
if element_value.to_s.include?(comparison_value.to_s)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
when :match
|
|
if element_value.to_s.match(comparison_value)
|
|
match_uuid_list << entry[:parent_uuid].to_sym
|
|
end
|
|
end
|
|
rescue Exception => cast_error
|
|
# Probably a type casting error (comparison of dissimilar types)
|
|
next
|
|
end
|
|
# end - comparison loop
|
|
end
|
|
#
|
|
match_uuid_list.each do |the_matched_uuid|
|
|
# Add to main found list
|
|
parent_record = @connector[:element_array].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
|
|
if parent_record
|
|
new_record = {}
|
|
selected.each do |the_selected_key|
|
|
if parent_record.keys.include?(the_selected_key)
|
|
new_record[(the_selected_key)] = parent_record[(the_selected_key)]
|
|
if ::GxG::valid_uuid?(new_record[(the_selected_key)])
|
|
new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
|
|
end
|
|
end
|
|
end
|
|
unless found_list.include?(new_record)
|
|
found_list << new_record
|
|
end
|
|
end
|
|
end
|
|
#
|
|
# unless last_index == element_index
|
|
# last_index = element_index
|
|
# end
|
|
end
|
|
#
|
|
else
|
|
# Criteria Expansion Here
|
|
end
|
|
end
|
|
else
|
|
@connector[:element_hash].filter(hash_criteria).each do |entry|
|
|
new_record = {}
|
|
selected.each do |the_key|
|
|
if ::GxG::valid_uuid?(entry[(the_key)])
|
|
new_record[(the_key)] = entry[(the_key)].to_sym
|
|
else
|
|
new_record[(the_key)] = entry[(the_key)]
|
|
end
|
|
end
|
|
found_list << new_record
|
|
end
|
|
#
|
|
unless hash_criteria[:format]
|
|
@connector[:element_array].filter(array_criteria).each do |entry|
|
|
new_record = {}
|
|
selected.each do |the_key|
|
|
if ::GxG::valid_uuid?(entry[(the_key)])
|
|
new_record[(the_key)] = entry[(the_key)].to_sym
|
|
else
|
|
new_record[(the_key)] = entry[(the_key)]
|
|
end
|
|
end
|
|
found_list << new_record
|
|
end
|
|
end
|
|
end
|
|
#
|
|
# Sorting Options Here: (Expansion)
|
|
if criteria[:order].is_any?(::Symbol, ::Hash)
|
|
ascending = true
|
|
sort_key = nil
|
|
if criteria[:order].is_a?(::Hash)
|
|
if criteria[:order][:ascending].is_a?(::Symbol)
|
|
sort_key = criteria[:order][:ascending]
|
|
end
|
|
if criteria[:order][:descending].is_a?(::Symbol)
|
|
sort_key = criteria[:order][:descending]
|
|
ascending = false
|
|
end
|
|
else
|
|
sort_key = criteria[:order]
|
|
end
|
|
if selected.include?(sort_key)
|
|
found_list = found_list.sort do |entry, another|
|
|
entry[(sort_key)] <=> another[(sort_key)]
|
|
end
|
|
unless ascending
|
|
found_list.reverse!
|
|
end
|
|
end
|
|
end
|
|
# Impose result set limit and offset
|
|
if limit.is_a?(::Integer)
|
|
if found_list.size > 0
|
|
if (offset + (limit - 1)) <= (found_list.size - 1)
|
|
found_list = found_list[((offset)..(offset + (limit - 1)))]
|
|
else
|
|
if offset <= (found_list.size - 1)
|
|
found_list = found_list[((offset)..(-1))]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
found_list.size.times do
|
|
result << found_list.shift
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Database not available"
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct Database"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:credential => (credential), :criteria => (criteria)}})
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def search_database(credential=nil, criteria={})
|
|
# Usage: search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]})
|
|
#
|
|
# criteria[:properties] :
|
|
# For all
|
|
# {:property_name => {:equals => value}}
|
|
# For :element_integer, :element_float, :element_bigdecimal
|
|
# {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
|
|
# For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
|
|
# {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
|
|
# For Text, Binary
|
|
# {:property_name => {:include => value}}
|
|
#
|
|
# criteria[:element] :
|
|
# similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
|
|
#
|
|
result = []
|
|
criterium = []
|
|
criteria_error = false
|
|
unless criteria.is_a?(::Hash)
|
|
criteria_error = true
|
|
end
|
|
unless criteria_error
|
|
common_criteria = criteria.clone
|
|
properties = (common_criteria.delete(:properties) || [])
|
|
unless properties.is_a?(::Array)
|
|
criteria_error = true
|
|
end
|
|
elements = (common_criteria.delete(:elements) || [])
|
|
unless elements.is_a?(::Array)
|
|
criteria_error = true
|
|
end
|
|
end
|
|
#
|
|
unless criteria_error
|
|
if properties.size > 0 && elements.size > 0
|
|
criteria_error = true
|
|
log_warn("You MUST select EITHER :properites OR :elements, not both.")
|
|
end
|
|
end
|
|
#
|
|
unless criteria_error
|
|
# Prepare criterium
|
|
if common_criteria[:select].is_a?(::Array)
|
|
unless common_criteria[:select].include?(:uuid)
|
|
common_criteria[:select] << :uuid
|
|
end
|
|
else
|
|
common_criteria[:select] = [:uuid]
|
|
end
|
|
#
|
|
if common_criteria[:ufs]
|
|
criterium << {:ufs => common_criteria[:ufs]}
|
|
end
|
|
if common_criteria[:format]
|
|
criterium << {:format => common_criteria[:format]}
|
|
end
|
|
#
|
|
if properties.size > 0
|
|
properties.each do |the_property_search|
|
|
criterium << ((common_criteria.clone).merge({:property => the_property_search}))
|
|
end
|
|
end
|
|
if elements.size > 0
|
|
elements.each do |the_element_search|
|
|
criterium << ((common_criteria.clone).merge({:element => the_element_search}))
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result_sets = []
|
|
# if properties.size == 0 && elements.size == 0
|
|
# result_sets << self.uuid_list(credential, criteria)
|
|
# else
|
|
criterium.each do |the_criteria|
|
|
result_sets << self.uuid_list(credential, the_criteria)
|
|
end
|
|
# end
|
|
scores = {}
|
|
result_sets.each do |the_result|
|
|
the_result.each do |the_answer|
|
|
if scores.keys.include?(the_answer[:uuid])
|
|
scores[(the_answer[:uuid])] = (scores[(the_answer[:uuid])] + 1)
|
|
else
|
|
scores[(the_answer[:uuid])] = 1
|
|
end
|
|
end
|
|
end
|
|
scores.keys.each do |the_uuid|
|
|
if scores[(the_uuid)] == result_sets.size
|
|
result_sets[0].each do |the_record|
|
|
if the_record[:uuid] == the_uuid
|
|
result << the_record
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
# Synchronize records: (and supports)
|
|
#
|
|
def synchronize_records(the_records=[], credential=nil, options={})
|
|
# Review : rewrite for new architecture.
|
|
result = {:status => :completed}
|
|
#
|
|
total_records_processed = 0
|
|
total_formats_processed = 0
|
|
# List of format uuids needed from the OTHER database to complete the sync.
|
|
formats_needed = []
|
|
# List of format records we are sharing.
|
|
formats_provided = []
|
|
# records on THIS database that are higher version than submitted version.
|
|
updates_provided = []
|
|
# In case something goes horribly wrong:
|
|
clean_up_list = []
|
|
#
|
|
begin
|
|
if self.alive?
|
|
if self.open?
|
|
unless ::GxG::valid_uuid?(credential)
|
|
raise ArgumentError, "You MUST provide a valid UUID as credential."
|
|
end
|
|
#
|
|
if the_records.is_a?(::String)
|
|
if the_records.base64?
|
|
the_records = the_records.decode64
|
|
end
|
|
if the_records.json?
|
|
the_records = ::JSON.parse(the_records,{:symbolize_names => true})
|
|
end
|
|
end
|
|
if the_records.is_a?(::Hash)
|
|
the_records = [(the_records)]
|
|
end
|
|
unless the_records.is_a?(::Array)
|
|
raise ArgumentError, "You MUST provide: Base64, JSON, Hash, or Array of operation Hashes."
|
|
end
|
|
# Process each operation in the_records, data is inline under :data key.
|
|
the_records.each do |the_operation|
|
|
if the_operation.is_a?(::Hash)
|
|
if [:merge, :delete, :fetch, :merge_format, :delete_format, :formats_provided, :formats_needed].include?(the_operation[:operation])
|
|
#
|
|
case the_operation[:operation]
|
|
when :merge
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Attempted to alter a read-only database."
|
|
end
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if entry[:format]
|
|
unless self.format_list({:uuid => (entry[:format])}).size > 0
|
|
raise Exception, "Attempted to merge with unknown :format #{entry[:format].inspect}. Synchronize formats first, then records."
|
|
end
|
|
end
|
|
# fetch existing:
|
|
# if existing - compare versions - if greater provide, if lesser update, else add to db.
|
|
queue = [(entry)]
|
|
while queue.size > 0 do
|
|
record = queue.shift
|
|
if ::GxG::valid_uuid?(record[:uuid])
|
|
formatting = {:format => nil, :constraint => nil}
|
|
existing = self.retrieve_by_uuid(record[:uuid], credential)
|
|
unless existing
|
|
# Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
|
|
unless @connector[(record[:type].to_s.downcase.to_sym)].filter({:uuid => record[:uuid].to_s}).count > 0
|
|
# This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
|
|
dbid = @connector[(record[:type].to_s.downcase.to_sym)].insert({:uuid => record[:uuid].to_s, :title => "Merged Persisted Object"})
|
|
self.assign_element_permission(record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
#
|
|
end
|
|
existing = self.retrieve_by_uuid(record[:uuid], credential)
|
|
end
|
|
if existing
|
|
# ### Compare versions
|
|
if existing.version > record[:version]
|
|
action = :provide
|
|
else
|
|
if existing.version < record[:version]
|
|
action = :update
|
|
else
|
|
action = :ignore
|
|
end
|
|
end
|
|
# ### Take Action
|
|
case action
|
|
when :provide
|
|
updates_provided << {:operation => :merge, :data => [(existing.export())]}
|
|
existing.deactivate
|
|
total_records_processed += 1
|
|
when :update
|
|
# ### Prelude
|
|
if record[:format].to_s.size > 0
|
|
formatting[:format] = record[:format].to_s
|
|
end
|
|
if record[:constraint].to_s.size > 0
|
|
formatting[:constraint] = record[:constraint].to_s
|
|
end
|
|
clean_up_list << existing
|
|
existing.get_reservation()
|
|
unless existing.write_permission?() && existing.write_reserved?()
|
|
raise Exception, "Failed to secure write permission and write reservation for this credential."
|
|
end
|
|
existing.title = (record[:title])
|
|
# ### Update Elements
|
|
if record[:content].is_a?(::Hash)
|
|
# Review : What to do / how to handle formatted existing Hashes??
|
|
unless existing.format
|
|
# Eliminate keys that the imported record does not have. (tracking structural changes)
|
|
existing.keys.each do |the_key|
|
|
unless record[:content].keys.include?(the_key)
|
|
temp_object = existing.delete(the_key)
|
|
if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
|
|
temp_object.destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
record[:content].each_pair do |property, element_record|
|
|
if element_record[:uuid]
|
|
# ### Element is Hash or Array
|
|
element = self.retrieve_by_uuid(element_record[:uuid], credential)
|
|
unless element
|
|
# ### Create blank element record and retrieve
|
|
# Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
|
|
unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0
|
|
# This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
|
|
dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"})
|
|
self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
#
|
|
end
|
|
element = self.retrieve_by_uuid(element_record[:uuid], credential)
|
|
end
|
|
if element
|
|
# ### Ensure Element Linking
|
|
# Review : Warning - absence of a :write permission can bugger this up.
|
|
clean_up_list << element
|
|
element.get_reservation()
|
|
unless element.write_permission?() && element.write_reserved?()
|
|
raise Exception, "Failed to secure write permission and write reservation for this credential."
|
|
end
|
|
existing[(property)] = element
|
|
# element.deactivate ??
|
|
end
|
|
# Note: return to processing queue to set its elements
|
|
queue << element_record
|
|
else
|
|
# ### Base Element
|
|
unless existing.version(property) > element_record[:version]
|
|
the_content = nil
|
|
case element_record[:type].to_s.downcase.to_sym
|
|
when :element_boolean, :element_integer, :element_float
|
|
the_content = element_record[:content]
|
|
when :element_bigdecimal
|
|
the_content = ::BigDecimal.new(element_record[:content].to_s)
|
|
when :element_datetime
|
|
the_content = ::DateTime::parse(element_record[:content].to_s)
|
|
when :element_text
|
|
the_content = element_record[:content].to_s
|
|
when :element_binary
|
|
the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64)
|
|
end
|
|
existing[(property)] = the_content
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if record[:content].is_a?(::Array)
|
|
# Eliminate elements that the imported record does not have. (tracking structural changes)
|
|
if existing.size > record[:content].size
|
|
((record[:content].size)..(existing.size - 1)).each do |the_indexer|
|
|
temp_object = existing.delete_at(the_indexer)
|
|
if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
|
|
temp_object.destroy()
|
|
end
|
|
end
|
|
end
|
|
record[:content].each_with_index do |element_record, indexer|
|
|
if element_record[:uuid]
|
|
# ### Element is Hash or Array
|
|
element = self.retrieve_by_uuid(element_record[:uuid], credential)
|
|
unless element
|
|
# ### Create blank element record and retrieve
|
|
# Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
|
|
unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0
|
|
# This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
|
|
dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"})
|
|
self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
|
|
#
|
|
end
|
|
element = self.retrieve_by_uuid(element_record[:uuid], credential)
|
|
end
|
|
if element
|
|
# ### Ensure Element Linking
|
|
# Review : Warning - absence of a :write permission can bugger this up.
|
|
clean_up_list << element
|
|
element.get_reservation()
|
|
unless element.write_permission?() && element.write_reserved?()
|
|
raise Exception, "Failed to secure write permission and write reservation for this credential."
|
|
end
|
|
existing[(indexer)] = element
|
|
# element.deactivate ??
|
|
end
|
|
# Note: return to processing queue to set its elements
|
|
queue << element_record
|
|
else
|
|
# ### Base Element
|
|
unless existing.version(indexer) > element_record[:version]
|
|
the_content = nil
|
|
case element_record[:type].to_s.downcase.to_sym
|
|
when :element_boolean, :element_integer, :element_float
|
|
the_content = element_record[:content]
|
|
when :element_bigdecimal
|
|
the_content = ::BigDecimal.new(element_record[:content].to_s)
|
|
when :element_datetime
|
|
the_content = ::DateTime::parse(element_record[:content].to_s)
|
|
when :element_text
|
|
the_content = element_record[:content].to_s
|
|
when :element_binary
|
|
the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64)
|
|
end
|
|
existing[(indexer)] = the_content
|
|
end
|
|
end
|
|
#
|
|
end
|
|
end
|
|
end
|
|
# ### Set Fromatting / Constraints
|
|
if existing.is_a?(::GxG::Database::PersistedHash)
|
|
if formatting[:format].to_s.size > 0
|
|
existing.format = (formatting[:format])
|
|
end
|
|
end
|
|
if existing.is_a?(::GxG::Database::PersistedArray)
|
|
if formatting[:constraint].to_s.size > 0
|
|
existing.constraint = (formatting[:constraint])
|
|
end
|
|
end
|
|
# ### Conclude Existing
|
|
existing.version = (record[:version])
|
|
existing.save
|
|
existing.deactivate
|
|
total_records_processed += 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
end
|
|
when :delete
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Attempted to alter a read-only database."
|
|
end
|
|
#
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
|
|
if existing
|
|
the_address = existing.db_address()
|
|
existing.deactivate
|
|
if self.element_destroy(credential,the_address[:table],the_address[:dbid])
|
|
total_records_processed += 1
|
|
else
|
|
log_warn("Record #{entry[:uuid].inspect} was not deleted.")
|
|
end
|
|
else
|
|
log_warn("Record #{entry[:uuid].inspect} was not found.")
|
|
end
|
|
else
|
|
log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
#
|
|
when :fetch
|
|
# wrap export record in a :merge operation and add to updates_provided.
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
|
|
if existing
|
|
updates_provided << {:operation => :merge, :data => [(existing.export())]}
|
|
existing.deactivate
|
|
total_records_processed += 1
|
|
else
|
|
log_warn("Record #{entry[:uuid].inspect} was not found.")
|
|
end
|
|
else
|
|
log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
#
|
|
when :merge_format
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Attempted to alter a read-only database."
|
|
end
|
|
# add format record if missing - check for existing, and provide if ours is newer.
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
existing = self.format_load({:uuid => entry[:uuid].to_sym})
|
|
if existing.is_a?(::Hash)
|
|
if existing[:version] < entry[:version]
|
|
if self.format_update(entry)
|
|
total_formats_processed += 1
|
|
else
|
|
log_warn("Could not update Format #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
if existing[:version] > entry[:version]
|
|
unless formats_provided.include?(existing)
|
|
formats_provided << existing
|
|
total_formats_processed += 1
|
|
end
|
|
end
|
|
else
|
|
if self.format_create(entry)
|
|
total_formats_processed += 1
|
|
else
|
|
log_warn("Could not create Format #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
else
|
|
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
when :delete_format
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Attempted to alter a read-only database."
|
|
end
|
|
# If the format is in use, log_warn that you're ignoring the directive.
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
if @connector[:element_hash].filter({:format => entry[:uuid].to_s}).count == 0 && @connector[:element_array].filter({:constraint => entry[:uuid].to_s}).count == 0
|
|
# Not in use, safe to delete
|
|
if self.format_destroy({:uuid => entry[:uuid].to_sym})
|
|
total_formats_processed += 1
|
|
end
|
|
else
|
|
log_warn("Format #{entry[:uuid].inspect} in use (ignorning).")
|
|
end
|
|
else
|
|
log_warn("Invalid format specifier on :delete directive (ignorning): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
when :formats_provided
|
|
unless self.db_permissions()[:write]
|
|
raise Exception, "Attempted to alter a read-only database."
|
|
end
|
|
# compare uuids against db-installed formats and add to formats_needed if missing.
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
if self.format_list({:uuid => (entry[:uuid])}).size == 0
|
|
record = {:uuid => (entry[:uuid].to_sym)}
|
|
unless formats_needed.include?(record)
|
|
formats_needed << record
|
|
total_formats_processed += 1
|
|
end
|
|
end
|
|
else
|
|
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
when :formats_needed
|
|
# Gather up existing format records to share.
|
|
manifest = []
|
|
if the_operation[:data].is_a?(::Array)
|
|
manifest = the_operation[:data]
|
|
else
|
|
if the_operation[:data].is_a?(::Hash)
|
|
manifest = [(the_operation[:data])]
|
|
else
|
|
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
|
|
end
|
|
end
|
|
manifest.each do |entry|
|
|
if ::GxG::valid_uuid?(entry[:uuid])
|
|
existing = self.format_load({:uuid => entry[:uuid].to_sym})
|
|
if existing.is_a?(::Hash)
|
|
unless formats_provided.include?(existing)
|
|
formats_provided << existing
|
|
total_formats_processed += 1
|
|
end
|
|
end
|
|
else
|
|
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
log_warn("Invalid operation (ignoring): #{the_operation[:operation].inspect}")
|
|
end
|
|
else
|
|
raise Exception, "Invalid formatted operation passed (MUST be a Hash): #{the_operation.inspect}"
|
|
end
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Database not available."
|
|
end
|
|
else
|
|
raise Exception, "Attempted to access a defunct database."
|
|
end
|
|
rescue Exception => the_error
|
|
clean_up_list.each do |the_object|
|
|
if the_object.alive?
|
|
the_object.release_reservation()
|
|
the_object.deactivate()
|
|
end
|
|
end
|
|
result[:status] = :failed
|
|
#log_error({:error => the_error, :parameters => {:records => (the_records), :options => (options)}})
|
|
log_error({:error => the_error, :parameters => {}})
|
|
end
|
|
result[:formats_needed] = formats_needed
|
|
result[:formats_provided] = formats_provided
|
|
result[:record_updates] = updates_provided
|
|
result[:records_processed] = total_records_processed
|
|
result[:formats_processed] = total_formats_processed
|
|
result
|
|
end
|
|
#
|
|
def sync_export(credential=nil, uuid_list=[], options={})
|
|
result = {:formats => {}, :records => []}
|
|
if uuid_list.is_any?(::Array, ::GxG::Database::PersistedArray)
|
|
uuid_list.each do |the_reference|
|
|
the_uuid = the_reference
|
|
if ::GxG::valid_uuid?(the_uuid)
|
|
the_object = self.retrieve_by_uuid(the_uuid, credential)
|
|
if the_object
|
|
object_record = the_object.export()
|
|
object_record.search do |item,selector,container|
|
|
if selector == :format || selector == :constraint
|
|
if item.to_s.size > 0
|
|
format_uuid = item
|
|
unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash)
|
|
format_sample = self.format_load({:uuid => format_uuid.to_s.to_sym})
|
|
format_sample[:content] = format_sample[:content].gxg_export()
|
|
result[:formats][(format_uuid.to_s.to_sym)] = format_sample
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result[:records] << object_record
|
|
#
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={})
|
|
result = false
|
|
#
|
|
if the_records.is_a?(::Hash)
|
|
if the_records[:formats].is_a?(::Hash)
|
|
op_frame = {:operation => :merge_format, :data => []}
|
|
the_records[:formats].each_pair do |the_uuid, the_format_record|
|
|
if the_format_record[:version].is_a?(::String)
|
|
the_format_record[:version] = ::BigDecimal.new(the_format_record[:version])
|
|
end
|
|
# gxg_import format content prior to sync update op
|
|
the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content])
|
|
op_frame[:data] << the_format_record
|
|
end
|
|
if op_frame[:data].size > 0
|
|
self.synchronize_records([(op_frame)],credential)
|
|
end
|
|
end
|
|
#
|
|
if the_records[:records].is_a?(::Array)
|
|
if the_records[:records].size > 0
|
|
the_records[:records].each do |the_record|
|
|
the_record.search do |value, selector, container|
|
|
if selector == :version && value.is_a?(::String)
|
|
container[(selector)] = ::BigDecimal.new(value)
|
|
end
|
|
end
|
|
end
|
|
self.synchronize_records([{:operation => :merge, :data => the_records[:records]}],credential)
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
# Class method for importing an export record as a detached structure.
|
|
def self.detached_import(import_record=nil)
|
|
result = nil
|
|
unless import_record.is_any?(::Hash, ::GxG::Database::PersistedHash)
|
|
raise Exception, "Malformed import record passed."
|
|
end
|
|
# {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}}
|
|
import_db = [{:target => ::GxG::Database::DetachedHash.new, :record => import_record}]
|
|
result = import_db[0][:target]
|
|
while import_db.size > 0 do
|
|
entry = import_db.shift
|
|
case entry[:record][:type].to_s.to_sym
|
|
when :element_hash
|
|
entry[:target].uuid = entry[:record][:uuid].to_s.to_sym
|
|
entry[:target].title = entry[:record][:title].to_s
|
|
entry[:record][:content].keys.each do |the_key|
|
|
case entry[:record][:content][(the_key)][:type].to_s.to_sym
|
|
when :element_hash
|
|
new_target = GxG::Database::DetachedHash.new
|
|
entry[:target][(the_key)] = new_target
|
|
import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])}
|
|
entry[:target][(the_key)].parent = (entry[:target])
|
|
when :element_array
|
|
new_target = GxG::Database::DetachedArray.new
|
|
entry[:target][(the_key)] = new_target
|
|
import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])}
|
|
entry[:target][(the_key)].parent = (entry[:target])
|
|
when :element_boolean, :element_integer, :element_float, :element_text
|
|
entry[:target][(the_key)] = entry[:record][:content][(the_key)][:content]
|
|
when :element_bigdecimal
|
|
entry[:target][(the_key)] = ::BigDecimal.new(entry[:record][:content][(the_key)][:content].to_s)
|
|
when :element_datetime
|
|
entry[:target][(the_key)] = ::Chronic::parse(entry[:record][:content][(the_key)][:content].to_s)
|
|
when :element_binary
|
|
entry[:target][(the_key)] = ::GxG::ByteArray.new(entry[:record][:content][(the_key)][:content].to_s.decode64)
|
|
end
|
|
end
|
|
entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s)
|
|
entry[:target].format = entry[:record][:format].to_s.to_sym
|
|
when :element_array
|
|
entry[:target].uuid = entry[:record][:uuid].to_s.to_sym
|
|
entry[:target].title = entry[:record][:title].to_s
|
|
entry[:record][:content].each_index do |indexer|
|
|
case entry[:record][:content][(indexer)][:type].to_s.to_sym
|
|
when :element_hash
|
|
new_target = GxG::Database::DetachedHash.new
|
|
entry[:target][(indexer)] = new_target
|
|
import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])}
|
|
entry[:target][(indexer)].parent =(entry[:target])
|
|
when :element_array
|
|
new_target = GxG::Database::DetachedArray.new
|
|
entry[:target][(indexer)] = new_target
|
|
import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])}
|
|
entry[:target][(indexer)].parent = (entry[:target])
|
|
when :element_boolean, :element_integer, :element_float, :element_text
|
|
entry[:target][(indexer)] = entry[:record][:content][(indexer)][:content]
|
|
when :element_bigdecimal
|
|
entry[:target][(indexer)] = ::BigDecimal.new(entry[:record][:content][(indexer)][:content].to_s)
|
|
when :element_datetime
|
|
entry[:target][(indexer)] = ::Chronic::parse(entry[:record][:content][(indexer)][:content].to_s)
|
|
when :element_binary
|
|
entry[:target][(indexer)] = ::GxG::ByteArray.new(entry[:record][:content][(indexer)][:content].to_s.decode64)
|
|
end
|
|
end
|
|
entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s)
|
|
entry[:target].constraint = entry[:record][:constraint].to_s.to_sym
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
# Class method to import an export package as a detached object.
|
|
def self.detached_package_import(the_records={:formats => {}, :records => []}, options={})
|
|
result = []
|
|
#
|
|
if the_records.is_a?(::Hash)
|
|
if the_records[:formats].is_a?(::Hash)
|
|
op_frame = {:operation => :merge_format, :data => []}
|
|
the_records[:formats].each_pair do |the_uuid, the_format_record|
|
|
if the_format_record[:version].is_a?(::String)
|
|
the_format_record[:version] = ::BigDecimal.new(the_format_record[:version])
|
|
end
|
|
# gxg_import format content prior to sync update op
|
|
the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content])
|
|
# Add to ::GxG::DB[:formats] ??
|
|
unless ::GxG::DB[:formats][(the_uuid.to_s.to_sym)]
|
|
# ?? Review : thread safety ??
|
|
::GxG::DB[:formats][(the_uuid.to_s.to_sym)] = the_format_record
|
|
end
|
|
op_frame[:data] << the_format_record
|
|
end
|
|
if op_frame[:data].size > 0
|
|
::GxG::DB[:roles][:formats].synchronize_records([(op_frame)],::GxG::DB[:administrator])
|
|
end
|
|
end
|
|
#
|
|
if the_records[:records].is_a?(::Array)
|
|
if the_records[:records].size > 0
|
|
the_records[:records].each do |the_record|
|
|
the_record.search do |value, selector, container|
|
|
if selector == :version && value.is_a?(::String)
|
|
container[(selector)] = ::BigDecimal.new(value)
|
|
end
|
|
end
|
|
#
|
|
result << ::GxG::Database::detached_import(the_record)
|
|
#
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
# ### Utility methods
|
|
def clear_all_element_locks()
|
|
# for when boo-boos happen
|
|
if self.open?()
|
|
if self.db_permissions()[:write]
|
|
@connector[:element_locks].filter({}).delete
|
|
end
|
|
end
|
|
end
|
|
#
|
|
# ### Various methods
|
|
# Comm objects cannot be serialized - return encoded nil
|
|
def serialize()
|
|
"marshal:BAgw"
|
|
end
|
|
#
|
|
# Comm objects cannot be cloned - returns nil
|
|
def initialize_clone
|
|
nil
|
|
end
|
|
alias :initialize_dup :initialize_clone
|
|
alias :dup :initialize_clone
|
|
def clone()
|
|
initialize_clone
|
|
end
|
|
#
|
|
def initialize(connection = nil, options={})
|
|
# TODO: require :credential (access privs)
|
|
super()
|
|
@credential = nil
|
|
@url = options[:url]
|
|
@base_permissions = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
|
|
#
|
|
if connection.is_a?(::Sequel::Database)
|
|
@connector = connection
|
|
#
|
|
@active = false
|
|
unless open?()
|
|
raise Exception, "Unable to connect with the database"
|
|
end
|
|
#
|
|
@scheme = @url.scheme.to_s.to_sym
|
|
case @scheme
|
|
when :sqlite, :sqlite3
|
|
@connector.run("PRAGMA page_size=65536")
|
|
@connector.run("PRAGMA max_page_count=281474976710656")
|
|
@connector.run("PRAGMA encoding='UTF-8'")
|
|
when :mysql, :mysql2
|
|
@connector.run("SET GLOBAL max_allowed_packet=17825792;")
|
|
@connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
|
|
@connector.run("SET collation_connection = 'utf8_general_ci';")
|
|
when :postgres
|
|
# place holder
|
|
end
|
|
else
|
|
raise Exception, "Invalid db connection"
|
|
end
|
|
# characterize basic effective permissions
|
|
begin
|
|
formatted = db_formatted?()
|
|
@base_permissions[:read] = true
|
|
@base_permissions[:execute] = true
|
|
rescue Exception => the_error
|
|
formatted = false
|
|
raise Exception, "Lack read permissions to the database"
|
|
end
|
|
#
|
|
unless options[:read_only]
|
|
if formatted
|
|
begin
|
|
dbid = @connector[:text_page].insert()
|
|
rescue Exception => the_error
|
|
dbid = nil
|
|
end
|
|
if dbid.is_a?(::Integer)
|
|
@connector[:text_page].filter({:dbid => (dbid)}).delete
|
|
@base_permissions[:write] = true
|
|
@base_permissions[:create] = true
|
|
@base_permissions[:destroy] = true
|
|
@base_permissions[:rename] = true
|
|
@base_permissions[:move] = true
|
|
end
|
|
else
|
|
begin
|
|
test_table_name = ::GxG::uuid_generate().gsub("-","")
|
|
while (@connector.table_exists?(test_table_name.to_sym)) do
|
|
test_table_name = ::GxG::uuid_generate().gsub("-","")
|
|
end
|
|
@connector.create_table(test_table_name.to_sym) do
|
|
primary_key :dbid
|
|
end
|
|
@connector.drop_table(test_table_name.to_sym)
|
|
@base_permissions[:write] = true
|
|
@base_permissions[:create] = true
|
|
@base_permissions[:destroy] = true
|
|
@base_permissions[:rename] = true
|
|
@base_permissions[:move] = true
|
|
rescue Exception => the_error
|
|
# Read-only Database
|
|
end
|
|
end
|
|
end
|
|
#
|
|
@base_permissions.freeze
|
|
#
|
|
unless formatted
|
|
if @base_permissions[:write]
|
|
db_format
|
|
unless db_formatted?()
|
|
@active = false
|
|
raise Exception, "Unable to format the database for use"
|
|
end
|
|
else
|
|
raise Exception, "Unable to format a read-only database"
|
|
end
|
|
end
|
|
#
|
|
@authority = nil
|
|
@db_list = []
|
|
if options[:authority].is_any?(::GxG::Database::Database, ::GxG::Database::LDAPAuthority)
|
|
@authority = options[:authority]
|
|
@authority.db_register(self)
|
|
end
|
|
#
|
|
unless @authority
|
|
unless self.role_exist?("Administrators")
|
|
if @base_permissions[:write]
|
|
if self.user_id_available?("root")
|
|
self.user_create("root","password")
|
|
temp_cred = self.user_credential("root","password")
|
|
temp_group = self.role_create("Administrators")
|
|
self.role_add_user(temp_group,temp_cred)
|
|
#
|
|
dev_group = self.role_create("Developers")
|
|
dsn_group = self.role_create("Designers")
|
|
usr_group = self.role_create("Users")
|
|
#
|
|
self[:system_credentials] = {:administrator => temp_cred, :administrators => temp_group, :developers => dev_group, :designers => dsn_group, :users => usr_group}
|
|
self[:permission_policy] = [{:event => :extend, :credential => :any, :action => {:extend_permissions => temp_group}}]
|
|
#
|
|
end
|
|
if self.user_id_available?("public")
|
|
self.user_create("public","password", "00000000-0000-4000-0000-000000000000")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
permission_policy_load
|
|
self
|
|
end
|
|
#
|
|
end
|
|
#
|
|
end
|
|
#
|
|
end
|
|
#
|
|
require File.expand_path("./gxg_database_persistedarray.rb",File.dirname(__FILE__))
|
|
require File.expand_path("./gxg_database_persistedhash.rb",File.dirname(__FILE__))
|
|
#
|