handle-system 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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'