handle-system 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in handle.gemspec
4
+ gemspec
5
+ gem 'simplecov', group: :development
data/LICENSE.txt ADDED
@@ -0,0 +1,34 @@
1
+ HANDLE RUBY GEM
2
+ ===============
3
+ Copyright (c) 2013 Michael Klein
4
+
5
+ MIT License
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+
27
+ HANDLE SYSTEM AND JAVA LIBRARIES
28
+ ================================
29
+ HANDLE SYSTEM, HANDLE.NET, HDL, HDL.NET, and GLOBAL HANDLE REGISTRY are trademarks owned by CNRI.
30
+
31
+ Copyright © Corporation for National Research Initiatives 2013; All Rights Reserved.
32
+
33
+ Made available under the Handle System Public License Agreement v.2
34
+ < http://hdl.handle.net/4263537/5030 >
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Handle [![Build Status](https://secure.travis-ci.org/mbklein/handle.png)](http://travis-ci.org/mbklein/handle)
2
+
3
+ Classes and methods for dealing with [Handle System](http://handle.net/) servers and handles.
4
+
5
+ ## Platform Notes
6
+
7
+ `Handle::Connection` and `Handle::Persistence` have two implementations each – one for JRuby,
8
+ and one for everything else. Under JRuby, it calls Java HSAdapter methods directly. Under MRI
9
+ or other non-JVM rubies, it shells out to command line tools (particularly `hdl-qresolver`
10
+ and `hdl-genericbatch`) behind the scenes to do its work.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'handle'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install handle
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require 'handle'
30
+
31
+ # Set up an authenticated connection
32
+ conn = Handle::Connection.new('0.NA/admin.handle', 300,
33
+ '/path/to/private/key/file', 'privkey-passphrase')
34
+
35
+ # Create an empty record
36
+ record = conn.create_record('handle.prefix/new.handle')
37
+
38
+ # Two ways to add fields
39
+ record.add(:URL, 'http://example.edu/').index = 2
40
+ record.add(:Email, 'someone@example.edu').index = 6
41
+ record << Handle::Field::HSAdmin.new('0.NA/admin.handle')
42
+
43
+ # Manipulate permissions
44
+ record.last.perms.public_read = false
45
+
46
+ record.save
47
+
48
+ record = conn.resolve_handle('handle.prefix/new.handle')
49
+ ```
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/tasklib'
3
+ require 'rdoc/task'
4
+
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'rspec/core/rake_task'
15
+ RSpec::Core::RakeTask.new do |t|
16
+ t.pattern = FileList['./spec/**/*_spec.rb']
17
+ end
18
+
19
+ task :default => :spec
20
+
21
+ Rake::RDocTask.new do |rdoc|
22
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
23
+
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = "handle #{version}"
26
+ rdoc.rdoc_files.include('README*')
27
+ rdoc.rdoc_files.include('lib/**/*.rb')
28
+ end
@@ -0,0 +1,134 @@
1
+ require 'tempfile'
2
+
3
+ module Handle
4
+ module Command
5
+ HDL_HOME = ENV['HDL_HOME'] || '/usr/local/handle'
6
+
7
+ class Batch
8
+ def initialize(handle, index, auth)
9
+ @batch_file = Tempfile.new('hdl')
10
+ auth_type = auth.length == 2 ? "PUBKEY" : "SECKEY"
11
+ @batch_file.puts "AUTHENTICATE #{auth_type}:#{index}:#{handle}"
12
+ @batch_file.puts auth.select { |p| not (p.nil? or p.empty?) }.join('|')
13
+ end
14
+
15
+ def cleanup
16
+ @batch_file.close unless @batch_file.closed?
17
+ @batch_file.unlink
18
+ end
19
+
20
+ def execute!
21
+ @batch_file.close
22
+ cmd = File.join(HDL_HOME, 'bin', 'hdl-genericbatch')
23
+ output = `#{cmd} #{@batch_file.path} 2>/dev/null`
24
+ results = output.lines.select { |line| line =~ /^=+>/ }
25
+ results.each do |rs|
26
+ (status, message) = rs.scan(/^=+>(.+)\[[0-9]+\]: (.+)/).flatten
27
+ (action, handle, code, message) = message.split(/:\s*/,4)
28
+ if status == 'FAILURE'
29
+ exception = Handle::HandleError.new message
30
+ exception.set_backtrace(caller[3..-1])
31
+ raise exception
32
+ end
33
+ end
34
+ return true
35
+ end
36
+
37
+ def add_handle_values(handle, record)
38
+ @batch_file.puts "\nADD #{handle}"
39
+ @batch_file.puts record.to_batch
40
+ end
41
+
42
+ def create_handle(handle, record)
43
+ @batch_file.puts "\nCREATE #{handle}"
44
+ @batch_file.puts record.to_batch
45
+ end
46
+
47
+ def delete_handle(handle)
48
+ @batch_file.puts "\nDELETE #{handle}"
49
+ end
50
+
51
+ def delete_handle_values(handle, record)
52
+ indexes = record.collect(&:index).join(',')
53
+ @batch_file.puts "\nREMOVE #{indexes}:#{handle}"
54
+ end
55
+
56
+ def update_handle_values(handle, record)
57
+ @batch_file.puts "\nMODIFY #{handle}"
58
+ @batch_file.puts record.to_batch
59
+ end
60
+ end
61
+
62
+ class Connection
63
+ def initialize(handle, index, *auth, &block)
64
+ @handle = handle
65
+ @index = index
66
+ @auth_params = auth
67
+ if block_given?
68
+ batch &block
69
+ end
70
+ end
71
+
72
+ def batch
73
+ context = Batch.new(@handle, @index, @auth_params)
74
+ begin
75
+ yield context
76
+ result = context.execute!
77
+ ensure
78
+ context.cleanup
79
+ end
80
+ result
81
+ end
82
+
83
+ def add_handle_values(*args)
84
+ batch { |b| b.add_handle_values(*args) }
85
+ end
86
+
87
+ def create_handle(*args)
88
+ batch { |b| b.create_handle(*args) }
89
+ end
90
+
91
+ def delete_handle(*args)
92
+ batch { |b| b.delete_handle(*args) }
93
+ end
94
+
95
+ def delete_handle_values(*args)
96
+ batch { |b| b.delete_handle_values(*args) }
97
+ end
98
+
99
+ def update_handle_values(*args)
100
+ batch { |b| b.update_handle_values(*args) }
101
+ end
102
+
103
+ def create_record(handle)
104
+ result = Handle::Record.new
105
+ result.connection = self
106
+ result.handle = handle
107
+ result
108
+ end
109
+
110
+ def resolve_handle(handle, types=[], indexes=[], auth=true)
111
+ cmd = File.join(HDL_HOME, 'bin', 'hdl-qresolver')
112
+ response = `#{cmd} #{handle} 2>/dev/null`.strip
113
+ if response =~ /^Got Response:/
114
+ response = response.lines.select { |line| line =~ /^\s*index=/ }.join("")
115
+ result = Handle::Record.from_data(response)
116
+ result.connection = self
117
+ result.handle = handle
118
+ result
119
+ else
120
+ (code, message) = response.lines.to_a.last.scan(/Error\(([0-9]+)\): (.+)$/).flatten
121
+ exception_klass = case code.to_i
122
+ when 100 then Handle::NotFound
123
+ else Handle::HandleError
124
+ end
125
+ exception = exception_klass.new message
126
+ exception.set_backtrace(caller)
127
+ raise exception
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ Connection = Command::Connection
134
+ end
@@ -0,0 +1,57 @@
1
+ module Handle
2
+ module Command
3
+ module Persistence
4
+ attr_accessor :handle, :connection
5
+
6
+ def to_batch
7
+ self.collect do |field|
8
+ perm_params = field.perms.to_s
9
+ data_type = case field.class.value_type
10
+ when 'HS_ADMIN' then 'ADMIN'
11
+ when 'HS_SITE' then 'FILE'
12
+ when 'HS_PUBKEY' then 'FILE'
13
+ else 'UTF8'
14
+ end
15
+ "#{field.index} #{field.class.value_type} #{field.ttl} #{perm_params} #{data_type} #{field.value_str}"
16
+ end
17
+ end
18
+
19
+ def reload
20
+ self.initialize_with(connection.resolve_handle(self.handle).fields)
21
+ end
22
+
23
+ def save(new_handle=nil)
24
+ save_handle = new_handle || self.handle
25
+ if save_handle.nil?
26
+ raise Handle::HandleError.new("No handle provided.")
27
+ end
28
+
29
+ if save_handle == self.handle
30
+ begin
31
+ original = connection.resolve_handle(save_handle)
32
+ actions = original | self
33
+ actions.each_value { |v| v.connection = connection }
34
+ [:delete,:update,:add].each do |action|
35
+ unless actions[action].empty?
36
+ connection.send("#{action}_handle_values".to_sym, save_handle, actions[action])
37
+ end
38
+ end
39
+ rescue Handle::NotFound
40
+ connection.create_handle(save_handle, self)
41
+ @handle = save_handle
42
+ end
43
+ else
44
+ connection.create_handle(save_handle, self)
45
+ @handle = save_handle
46
+ end
47
+ self
48
+ end
49
+
50
+ def destroy
51
+ connection.delete_handle(self.handle)
52
+ end
53
+ end
54
+ end
55
+ Persistence = Command::Persistence
56
+ end
57
+
@@ -0,0 +1,2 @@
1
+ require 'handle/command/connection'
2
+ require 'handle/command/persistence'
@@ -0,0 +1,79 @@
1
+ module Handle
2
+ module Field
3
+ class HSAdmin < Base
4
+ value_type 'HS_ADMIN'
5
+ default_index 100
6
+
7
+ attr_accessor :admin_handle
8
+ attr :admin_perms, :admin_index
9
+
10
+ def initialize(handle=nil)
11
+ super()
12
+ @admin_handle = handle
13
+ @admin_index = 300
14
+ @admin_perms = Handle::Permissions.new(
15
+ :add_handle, :delete_handle, :add_na, :delete_na,
16
+ :modify_values, :remove_values, :add_values, :read_values,
17
+ :modify_admin, :remove_admin, :add_admin, :list_handles,
18
+ )
19
+ @admin_perms.bitmask = 0b111111111111
20
+ end
21
+
22
+ def value
23
+ values = [self.admin_perms.bitmask, 0, 13, self.admin_handle, 0, self.admin_index]
24
+ values.pack('nnnZ*cn')
25
+ end
26
+
27
+ def value_str
28
+ [self.admin_index,self.admin_perms.to_s,self.admin_handle].join(':')
29
+ #value.bytes.collect { |b| '%2.2X' % b }.join('')
30
+ end
31
+
32
+ def value=(bytes)
33
+ if bytes =~ /^([0-9]+):([01]+):(.+)$/
34
+ self.admin_index = $1.to_i
35
+ self.admin_perms.bitmask = $2.to_i(2)
36
+ self.admin_handle = $3
37
+ else
38
+ if bytes =~ /^[0-9A-Fa-f]+$/
39
+ bytes = bytes.scan(/../).map(&:hex).pack('C*')
40
+ end
41
+ values = bytes.unpack('nnnZ*cn')
42
+ self.admin_perms.bitmask = values[0]
43
+ self.admin_handle = values[3]
44
+ self.admin_index = values[5] unless values[5].nil? or values[5] == 0
45
+ end
46
+ end
47
+
48
+ def to_h
49
+ result = super.merge({
50
+ admin_handle: self.admin_handle,
51
+ admin_index: self.admin_index,
52
+ admin_perms: self.admin_perms.bitmask
53
+ })
54
+ result.delete(:value)
55
+ result
56
+ end
57
+
58
+ def to_s
59
+ " index=#{self.index} type=#{self.class.value_type} ttl=#{self.ttl} #{self.perms} #{value_str.inspect}"
60
+ end
61
+
62
+ def admin_index=(value)
63
+ @admin_index = value.to_i
64
+ end
65
+
66
+ def merge!(hash)
67
+ hash.each_pair do |key,value|
68
+ k = key.to_sym
69
+ self.perms.bitmask = value if k == :perms
70
+ self.admin_perms.bitmask = value if k == :admin_perms
71
+ if [:admin_handle, :admin_index, :index, :ttl, :value].include? k
72
+ self.send(:"#{k.to_s}=", value)
73
+ end
74
+ end
75
+ self
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,125 @@
1
+ module Handle
2
+ module Field
3
+ class Base
4
+ attr_accessor :value
5
+ attr :index, :ttl, :perms
6
+
7
+ class << self
8
+ @@value_types = {}
9
+ def value_type val=nil
10
+ if val
11
+ if @value_type
12
+ @@value_types.delete(@value_type)
13
+ end
14
+ @value_type = val
15
+ @@value_types[val] = self
16
+ end
17
+ @value_type
18
+ end
19
+
20
+ def default_index val=nil
21
+ val ? @default_index = val : @default_index
22
+ end
23
+
24
+ def from_hash(hash)
25
+ type = hash.values_at(:type,'type').compact.first
26
+ if @@value_types.has_key? type
27
+ klass = @@value_types[type]
28
+ result = klass.new
29
+ result.merge!(hash)
30
+ end
31
+ end
32
+
33
+ def from_string(str)
34
+ attrs, perms, data = str.scan(/^\s*(.+) ([10rw\-]+) "(.+)"$/).flatten
35
+ attrs = attrs.split(/\s+/).inject({}) { |hash,attr|
36
+ (k,v) = attr.split(/\=/,2)
37
+ hash[k.to_sym] = v
38
+ hash
39
+ }
40
+ type = attrs.delete(:type)
41
+ if @@value_types.has_key? type
42
+ klass = @@value_types[type]
43
+ result = klass.new
44
+ result.merge!(attrs)
45
+ result.perms.bitmask = perms.gsub(/./) { |m| m =~ /[0\-]/ ? '0' : '1' }.to_i(2)
46
+ result.value = data
47
+ result
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ def from_data(data)
54
+ if data.is_a?(Hash)
55
+ self.from_hash(data)
56
+ else
57
+ self.from_string(data.to_s)
58
+ end
59
+ end
60
+ end
61
+
62
+ def initialize
63
+ @index = self.class.default_index
64
+ @ttl = 86400
65
+ @perms = Handle::Permissions.new(:admin_read, :admin_write, :public_read, :public_write, 0b1110)
66
+ end
67
+
68
+ def ==(other)
69
+ self.to_s == other.to_s
70
+ end
71
+
72
+ def index=(value)
73
+ @index = value.to_i
74
+ end
75
+
76
+ def ttl=(value)
77
+ @ttl = value.to_i
78
+ end
79
+
80
+ def merge!(hash)
81
+ hash.each_pair do |key,value|
82
+ k = key.to_sym
83
+ value = value.to_i if [:perms,:index].include?(k)
84
+ self.perms.bitmask = value if k == :perms
85
+ if [:index, :ttl, :value].include? k
86
+ self.send(:"#{k.to_s}=", value)
87
+ end
88
+ end
89
+ self
90
+ end
91
+
92
+ def to_h
93
+ {
94
+ index: self.index,
95
+ type: self.class.value_type,
96
+ ttl: self.ttl,
97
+ perms: self.perms.bitmask,
98
+ value: self.value
99
+ }
100
+ end
101
+
102
+ def to_json *args
103
+ self.to_h.to_json *args
104
+ end
105
+
106
+ def to_s
107
+ " index=#{self.index} type=#{self.class.value_type} ttl=#{self.ttl} #{self.perms} #{self.value.inspect}"
108
+ end
109
+
110
+ def value_str
111
+ value.to_s
112
+ end
113
+ end
114
+
115
+ class URL < Base ; value_type 'URL' ; end
116
+ class URN < Base ; value_type 'URN' ; end
117
+ class Email < Base ; value_type 'EMAIL' ; end
118
+ class HSSite < Base ; value_type 'HS_SITE' ; end
119
+ class HSServ < Base ; value_type 'HS_SERV' ; end
120
+ class HSAlias < Base ; value_type 'HS_ALIAS' ; end
121
+ class HSPubKey < Base ; value_type 'HS_PUB_KEY' ; default_index 300 ; end
122
+ class HSSecKey < Base ; value_type 'HS_SEC_KEY' ; default_index 301 ; end
123
+ class HSVList < Base ; value_type 'HS_VLIST' ; default_index 400 ; end
124
+ end
125
+ end
@@ -0,0 +1,95 @@
1
+ module Handle
2
+ module Java
3
+ class Connection
4
+ # A more Ruby-ish HSAdapter
5
+
6
+ def initialize(handle, index, *auth)
7
+ # accept either a key file or the key itself
8
+ if auth.length == 2 and (not auth[0].bytes.to_a.include?(0)) and File.exists?(auth[0])
9
+ auth[0] = File.read(auth[0])
10
+ end
11
+ auth_params = auth.collect { |p| p.to_java_bytes }
12
+ protect {
13
+ @conn = Native::HSAdapterFactory.new_instance(handle, index, *auth_params)
14
+ }
15
+ end
16
+
17
+ def add_handle_values(handle, record)
18
+ protect {
19
+ @conn.addHandleValues(handle, record.to_java)
20
+ }
21
+ end
22
+
23
+ def create_handle(handle, record)
24
+ protect {
25
+ @conn.createHandle(handle, record.to_java)
26
+ }
27
+ end
28
+
29
+ def create_record(handle)
30
+ result = Handle::Record.new
31
+ result.connection = self
32
+ result.handle = handle
33
+ result
34
+ end
35
+
36
+ def delete_handle(handle)
37
+ protect {
38
+ @conn.deleteHandle(handle)
39
+ }
40
+ end
41
+
42
+ def delete_handle_values(handle, record)
43
+ protect {
44
+ @conn.deleteHandleValues(handle, record.to_java)
45
+ }
46
+ end
47
+
48
+ def resolve_handle(handle, types=[], indexes=[], auth=true)
49
+ protect {
50
+ java_response = @conn.resolveHandle(
51
+ handle, types.to_java(:string),
52
+ indexes.to_java(:int), auth
53
+ )
54
+ result = Handle::Record.from_data(java_response)
55
+ result.connection = self
56
+ result.handle = handle
57
+ result
58
+ }
59
+ end
60
+
61
+ def use_udp=(value)
62
+ protect {
63
+ @conn.setUseUDP(value)
64
+ }
65
+ end
66
+
67
+ def update_handle_values(handle, record)
68
+ protect {
69
+ @conn.updateHandleValues(handle, record.to_java)
70
+ }
71
+ end
72
+
73
+ def native
74
+ @conn
75
+ end
76
+
77
+ protected
78
+ def protect
79
+ begin
80
+ response = yield
81
+ rescue Native::HandleException => err
82
+ exception_klass = case err.getCode()
83
+ when 9 then Handle::NotFound
84
+ else Handle::HandleError
85
+ end
86
+ exception = exception_klass.new err.message
87
+ exception.set_backtrace(caller)
88
+ raise exception
89
+ end
90
+ response.nil? ? true : response
91
+ end
92
+ end
93
+ end
94
+ Connection = Java::Connection
95
+ end
@@ -0,0 +1,54 @@
1
+ module Handle
2
+ module Java
3
+ module Persistence
4
+ attr_accessor :handle, :connection
5
+
6
+ def to_java
7
+ result = self.collect do |field|
8
+ perm_params = field.perms.to_bool
9
+ Native::HandleValue.new(field.index.to_java(:int), field.class.value_type.to_java_bytes,
10
+ field.value.to_java_bytes, Native::HandleValue::TTL_TYPE_RELATIVE.to_java(:byte),
11
+ field.ttl.to_java(:int), 0.to_java(:int), nil, *perm_params)
12
+ end
13
+ result.to_java(Native::HandleValue)
14
+ end
15
+
16
+ def reload
17
+ self.initialize_with(connection.resolve_handle(self.handle).fields)
18
+ end
19
+
20
+ def save(new_handle=nil)
21
+ save_handle = new_handle || self.handle
22
+ if save_handle.nil?
23
+ raise Handle::HandleError.new("No handle provided.")
24
+ end
25
+
26
+ if save_handle == self.handle
27
+ begin
28
+ original = connection.resolve_handle(save_handle)
29
+ actions = original | self
30
+ actions.each_value { |v| v.connection = connection }
31
+ [:delete,:update,:add].each do |action|
32
+ unless actions[action].empty?
33
+ connection.send("#{action}_handle_values".to_sym, save_handle, actions[action])
34
+ end
35
+ end
36
+ rescue Handle::NotFound
37
+ connection.create_handle(save_handle, self)
38
+ @handle = save_handle
39
+ end
40
+ else
41
+ connection.create_handle(save_handle, self)
42
+ @handle = new_handle
43
+ end
44
+ self
45
+ end
46
+
47
+ def destroy
48
+ connection.delete_handle(self.handle)
49
+ end
50
+ end
51
+ end
52
+ Persistence = Java::Persistence
53
+ end
54
+
@@ -0,0 +1,18 @@
1
+ require 'java'
2
+ # Load all jarfiles in $HDL_HOME/lib
3
+ hdl_home = ENV['HDL_HOME'] || File.expand_path('../../../vendor/handle',__FILE__)
4
+ Dir[File.join(hdl_home,'lib','*.jar')].each { |f| require f }
5
+
6
+ module Handle
7
+ module Java
8
+ module Native
9
+ java_import 'net.handle.hdllib.HandleException'
10
+ java_import 'net.handle.hdllib.HandleValue'
11
+ java_import 'net.handle.api.HSAdapter'
12
+ java_import 'net.handle.api.HSAdapterFactory'
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'handle/java/connection'
18
+ require 'handle/java/persistence'