nolij_web 1.0.0

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/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ lib/try.rb
20
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nolijweb.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Shannon Henderson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,50 @@
1
+ = NolijWeb
2
+
3
+ Talk to the Nolij Web API. The first version of this gem contains a small subset of available API methods. Document submission/printing(retrieval) are the main focus. Methods for folder listings, work flow request, and generated urls for the viewer are provided.
4
+
5
+ The raw connection can be used to execute other types of requests.
6
+
7
+ == Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'nolij_web'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install nolij_web
20
+
21
+ == Basic Usage
22
+
23
+ You should be able to use the handler for basic requests.
24
+ require 'nolij_web'
25
+
26
+ handler = NolijWeb::Handler.new(config_hash_or_path_to_config_yaml)
27
+
28
+ handler.folder_info({:folder_id => 1111111})
29
+
30
+ Configuration can be passed as an assigned hash or read from a YAML file.
31
+ config = {
32
+ :username => 'username',
33
+ :password => 'password',
34
+ :base_url => 'https://somedomain.com/NolijWeb'
35
+ }
36
+
37
+ == Passing a block to NolijWeb::Handler methods
38
+ You may pass a custom block to handler methods. Please note that your block should return the response for successful request. Some of the methods rely on parsing XML responses from the server.
39
+
40
+ == Using the Connection Directly
41
+
42
+ You can use the handler's connection directly. See NolijWeb::Connection for info.
43
+
44
+ == Contributing
45
+
46
+ 1. Fork it
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ module NolijWeb
2
+ class AttributeMissingError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module NolijWeb
2
+ class AuthenticationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,179 @@
1
+
2
+ require 'rest_client'
3
+ require 'logger'
4
+
5
+ module NolijWeb
6
+ =begin rdoc
7
+ Nolijweb::Connection is the class that handles actual Nolijweb api sessions and requests.
8
+
9
+ ===Basic Usage
10
+
11
+ #get, #post, #delete will automatically wrap your request in open and close connection. Arguments are passed through to RestClient, so see RestClient docs for more options.
12
+
13
+ conn = NolijWeb::Connection.new(config_hash_or_yaml_path)
14
+ conn.get('/print', :query_params => { :document_id => 1})
15
+
16
+ ===Manual Usage
17
+ You can manually establish a connection and use the _custom_connection methods to execute multiple requests in series.
18
+
19
+ Be sure the close the connection when you are finished.
20
+
21
+ conn = NolijWeb::Connection.new(config_hash_or_yaml_path)
22
+ conn.establish_connection
23
+ # do some stuff using get, post, etc. (_custom_connection methods)
24
+ close_connection
25
+ =end
26
+ class Connection
27
+ attr_reader :base_url
28
+ attr_reader :username
29
+ attr_reader :cookies
30
+ attr_reader :connection
31
+
32
+ @@valid_config_keys = [:username, :password, :base_url]
33
+
34
+ def initialize(config)
35
+ if config.is_a?(String)
36
+ configure_with(config)
37
+ elsif config.is_a?(Hash)
38
+ configure(config)
39
+ else
40
+ raise ConnectionConfigurationError, 'Invalid configuration options supplied.'
41
+ end
42
+ end
43
+
44
+ # Configure using hash
45
+ def configure(opts = {})
46
+ @config = clean_config_hash(opts)
47
+
48
+ raise ConnectionConfigurationError, 'Nolij Web Connection configuration failed.' unless @config
49
+
50
+ @base_url = @config[:base_url] || ''
51
+ @username = @config[:username] || ''
52
+ @password = @config[:password] || ''
53
+ @connection = nil
54
+ @cookies = nil
55
+ @headers = {}
56
+ end
57
+
58
+ # Configure with yaml
59
+ def configure_with(path_to_yaml_file)
60
+ raise ConnectionConfigurationError, "Invalid request. #configure_with requires string" unless path_to_yaml_file.is_a?(String)
61
+ begin
62
+ @config = YAML::load(IO.read(path_to_yaml_file))
63
+ rescue Errno::ENOENT
64
+ raise ConnectionConfigurationError, "YAML configuration file was not found."
65
+ return
66
+ rescue Psych::SyntaxError
67
+ raise ConnectionConfigurationError, "YAML configuration file contains invalid syntax."
68
+ return
69
+ end
70
+
71
+ configure(@config)
72
+ end
73
+
74
+ def establish_connection
75
+ @connection = RestClient.post("#{@base_url}/j_spring_security_check", {:j_username => @username, :j_password => @password}) { |response, request, result, &block|
76
+ if [301, 302, 307].include? response.code
77
+ response
78
+ else
79
+ response.return!(request, result, &block)
80
+ end
81
+ }
82
+ @cookies = @connection.cookies if @connection
83
+ @headers = {:cookies => @cookies}
84
+ return true if @connection
85
+ end
86
+
87
+ def close_connection
88
+ RestClient.get("#{@base_url}/j_spring_security_logout", :cookies => @cookies) if @connection
89
+ @connection = nil
90
+ @cookies = nil
91
+ @headers = {}
92
+ return true
93
+ end
94
+
95
+ def get(path, headers = {}, &block)
96
+ execute(headers) do
97
+ get_custom_connection(path, @headers, &block)
98
+ end
99
+ end
100
+
101
+ def delete(path, headers = {}, &block)
102
+ execute(headers) do
103
+ delete_custom_connection(path, @headers, &block)
104
+ end
105
+ end
106
+
107
+ def post(path, payload, headers = {}, &block)
108
+ execute(headers) do
109
+ post_custom_connection(path, payload, @headers, &block)
110
+ end
111
+ end
112
+
113
+ def execute(headers = {}, &block)
114
+ establish_connection
115
+ if @connection
116
+ merge_headers(headers)
117
+ yield(block)
118
+ end
119
+ ensure
120
+ close_connection
121
+ end
122
+
123
+ # Use this inside an execute block to make mulitiple calls in the same request
124
+ def get_custom_connection(path, headers = {}, &block)
125
+ block ||= default_response_handler
126
+ url = URI.join(@base_url, URI.parse(@base_url).path + '/', path.to_s).to_s
127
+ RestClient.get(url, headers, &block)
128
+ end
129
+
130
+ # Use this inside an execute block to make mulitiple calls in the same request
131
+ def delete_custom_connection(path, headers = {}, &block)
132
+ block ||= default_response_handler
133
+ url = URI.join(@base_url, URI.parse(@base_url).path + '/', path.to_s).to_s
134
+ RestClient.delete(url, headers, &block)
135
+ end
136
+
137
+ # Use this inside an execute block to make mulitiple calls in the same request
138
+ def post_custom_connection(path, payload, headers = {}, &block)
139
+ block ||= default_response_handler
140
+ url = URI.join(@base_url, URI.parse(@base_url).path + '/', path.to_s).to_s
141
+ RestClient.post(url, payload, headers, &block)
142
+ end
143
+
144
+ private
145
+
146
+ def clean_config_hash(config)
147
+ clean_config = {}
148
+ config = config.each {|k,v| config[k.to_sym] = v}
149
+ @@valid_config_keys.each{|k| clean_config[k] = config[k]}
150
+
151
+ return clean_config
152
+ end
153
+
154
+ def merge_headers(headers = {})
155
+ instance_cookies = @headers.delete(:cookies) || {}
156
+ local_cookies = headers.delete(:cookies) || {}
157
+ @headers = @headers.merge(headers)
158
+ @headers[:cookies] = instance_cookies.merge(local_cookies)
159
+ return @headers
160
+ end
161
+
162
+ def default_response_handler
163
+ @default_response_handler ||= lambda{ |response, request, result, &block|
164
+ case response.code
165
+ when 200
166
+ #Success!
167
+ return response
168
+ when 302
169
+ raise AuthenticationError, 'User is not logged in.'
170
+ when 401
171
+ raise AuthenticationError, 'Request requires authentication'
172
+ else
173
+ # Some other error. Let it bubble up.
174
+ response.return!(request, result, &block)
175
+ end
176
+ }
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,4 @@
1
+ module NolijWeb
2
+ class ConnectionConfigurationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,208 @@
1
+ require 'nokogiri'
2
+ require 'uri'
3
+ module NolijWeb
4
+ class Handler
5
+ @@doc_handler_path = 'handler/api/docs'
6
+ @@doc_viewer_path = ''
7
+ @@workflow_path = 'handler/api/workflow/workcomplete'
8
+ @@api_path = 'handler/api'
9
+
10
+ attr_reader :connection
11
+
12
+ def initialize(connection_config)
13
+ @config = connection_config
14
+ @connection = Connection.new(@config)
15
+ end
16
+
17
+ # Folder contents
18
+ # required options: :folder_id
19
+ # additional options: :user_code, :user_id, :sort, :offset, :limit, :wfma_code
20
+ def folder_info(options = {}, &block)
21
+ folder_id = options[:folder_id]
22
+ raise AttributeMissingError, 'Folder ID is required.' unless folder_id.is_a?(String) && !folder_id.empty?
23
+
24
+ allowed_query_params_keys = [:user_code, :user_id, :sort, :offset, :limit, :wfma_code]
25
+ query_params = format_query_params(options, allowed_query_params_keys)
26
+ headers = options[:headers] || {}
27
+ relative_path = [@@doc_handler_path, options[:folder_id]].join('/')
28
+
29
+ response = @connection.get relative_path, headers.merge(:params => query_params), &block
30
+
31
+ folder = Nokogiri.XML(response)
32
+ end
33
+
34
+ # Returns an array of hashes with the file info for each file in the folder
35
+ # See #folder_info for options
36
+ def folder_contents(options = {}, &block)
37
+ folder = folder_info(options, &block)
38
+ files = folder.xpath('//folderobjects//folderobject')
39
+
40
+ file_attrs = files.collect(&:attributes)
41
+ file_attrs = file_attrs.collect {|attr|
42
+ attr.inject({}){|n, (k,v)| n[k] = v.value; n}
43
+ }
44
+ end
45
+
46
+ # Submit a file.
47
+ # A local file path is required.
48
+ # required options: :folder_id
49
+ # additional options: :user_code, :wfma_code, :index_code, :dept_code, :custom_name, :folder_name
50
+ def submit_document(local_file, options = {}, &block)
51
+ folder_id = options[:folder_id]
52
+ folder_id = if folder_id.kind_of?(Numeric) || folder_id.kind_of?(String)
53
+ folder_id.to_s
54
+ else
55
+ nil
56
+ end
57
+ raise AttributeMissingError, 'Folder ID is required to submit a document.' unless folder_id.is_a?(String) && !folder_id.empty?
58
+
59
+ local_file ||= ''
60
+ file = begin
61
+ File.new(local_file)
62
+ rescue Errno::ENOENT
63
+ end
64
+
65
+ raise AttributeMissingError, 'Valid file or local filepath is required to submit a document.' unless file.is_a?(File)
66
+ options[:file_name] = File.basename(file.path)
67
+ allowed_query_params_keys = [:user_code, :wfma_code, :index_code, :dept_code, :custom_name, :file_name]
68
+ formatted_query = query_str(:allowed_query_params_keys => allowed_query_params_keys , :query_params => options)
69
+ relative_path = [@@doc_handler_path, folder_id].join('/') + formatted_query
70
+
71
+ # TODO custom attributes?
72
+ form_params = {}
73
+ form_params[:ocrwords] = options[:ocr_words] if options.has_key?(:oci_words)
74
+ form_params[:my_file] = file
75
+
76
+ #upload file to nolijweb
77
+ response = @connection.post relative_path, form_params, (options[:headers] || {}), &block
78
+
79
+ # return Nolij file id
80
+ doc_metadata = Nokogiri.XML(response)
81
+ document_id = doc_metadata.xpath('//documentmeta/@documentid').first
82
+ return document_id.value if document_id
83
+ end
84
+
85
+ # Print one or more documents to a single pdf
86
+ # required options: document_ids - can be a numer or an array of numbers
87
+ # additional options: :user_code, :document_id, :user_id
88
+ def print_document(options = {}, &block)
89
+ doc_ids = options.delete(:document_id)
90
+ raise AttributeMissingError, 'At least one document ID is required to print a document.' unless doc_ids
91
+ options[:document_id] = [doc_ids].flatten.compact.collect(&:to_i).join('-')
92
+
93
+ allowed_query_params_keys = [:user_code, :wfma_code, :user_id, :document_id]
94
+ query_params = format_query_params(options, allowed_query_params_keys)
95
+ headers = options[:headers] || {}
96
+ relative_path = [@@doc_handler_path, 'print'].join('/')
97
+
98
+ @connection.get relative_path, headers.merge(:params => query_params), &block
99
+ end
100
+
101
+ # Delete a document
102
+ # required options: :document_id, :folder_id
103
+ # additional options: :user_code, :user_id, :wfma_code
104
+ def delete_document(options = {}, &block)
105
+ folder_id = options[:folder_id]
106
+ document_id = options[:document_id]
107
+ raise AttributeMissingError, 'Folder ID is required to delete a document.' unless folder_id.is_a?(String) && ! folder_id.empty?
108
+ raise AttributeMissingError, 'Document ID is required to delete a document.' unless document_id.is_a?(String) && ! document_id.empty?
109
+
110
+ allowed_query_params_keys = [:user_code, :user_id, :wfma_code]
111
+ query_params = format_query_params(options, allowed_query_params_keys)
112
+ headers = options[:headers] || {}
113
+ relative_path = [@@doc_handler_path, 'delete', folder_id, document_id].join('/') + formatted_query
114
+
115
+ @connection.delete relative_path, headers.merge(:params => query_params), &block
116
+ end
117
+
118
+ # URL or path to verify that user is authenticated to Nolijweb in their browser.
119
+ # If the user is logged in redirect them to :redir path.
120
+ # If not, redirect to login and return them to the URL provided once they've authenticated.
121
+ # Redirect path must be relative to /public
122
+ # :redir is required. A path is returned unless :full_url => true
123
+ def login_check(options = {})
124
+ raise AttributeMissingError, 'Redirect path is required to check login' unless options[:redir].is_a?(String) && !options[:redir].empty?
125
+
126
+ full_url = options.delete(:full_url) || false
127
+ allowed_query_params_keys = [:redir]
128
+ formatted_query = query_str(:allowed_query_params_keys => allowed_query_params_keys , :query_params => options)
129
+
130
+ relative_path = ['public', 'apiLoginCheck.jsp'].join('/') + formatted_query
131
+ path = relative_path
132
+ url = full_url ? [@connection.base_url, path].join('/') : path
133
+ return url
134
+ end
135
+
136
+ # URL or path to open the standalone document viewer utility.
137
+ # required options: :document_id is required
138
+ # additional options: :user_code, :wfma_code
139
+ # use option :full_url => true for a full url, otherwise a relative path is returned.
140
+ def viewer_url(options = {})
141
+ raise AttributeMissingError, 'Document ID is required to launch viewer' unless (options[:document_id].is_a?(String) && !options[:document_id].empty?) || options[:document_id].is_a?(Integer)
142
+ full_url = options.delete(:full_url) || false
143
+ url_opts = {}
144
+ allowed_query_params_keys = [:document_id, :user_code, :wfma_code]
145
+ formatted_query = query_str(:allowed_query_params_keys => allowed_query_params_keys , :query_params => options)
146
+ relative_path = [@@doc_viewer_path, 'documentviewer'].join('/') + formatted_query
147
+ path = relative_path
148
+ url = full_url ? [@connection.base_url, path].join('/') : path
149
+ return url
150
+ end
151
+
152
+ # Issue work complete to push item along in work flow
153
+ # required options: :wfma_code, :folder_name
154
+ # additional options: :user_id, :user_code
155
+ def work_complete(options = {}, &block)
156
+ raise AttributeMissingError, 'Workflow master code is required for workflow requests.' unless options[:wfma_code]
157
+ folder_id = options[:folder_id]
158
+ raise AttributeMissingError, 'Folder ID is required.' unless folder_id
159
+
160
+ raise AttributeMissingError, 'Folder name is required.' unless options[:folder_name]
161
+
162
+ allowed_query_params_keys = [:wfma_code, :user_id, :user_code, :folder_name]
163
+ formatted_query = query_str(:allowed_query_params_keys => allowed_query_params_keys , :query_params => options)
164
+ relative_path = [@@workflow_path, folder_id, formatted_query].join('/')
165
+
166
+ @connection.post relative_path, {}, options[:headers] || {}, &block
167
+ end
168
+
169
+ # Nolij Web server version information
170
+ def version(&block)
171
+ relative_path = [@@api_path, 'version'].join('/')
172
+ response = @connection.get relative_path, &block
173
+
174
+ info = Nokogiri.XML(response).xpath('//version').collect(&:attributes)
175
+ info= info.collect {|attr|
176
+ attr.inject({}){|n, (k,v)| n[k] = v.value; n}
177
+ }.first
178
+ end
179
+
180
+ # TODO Add query and query results methods
181
+
182
+ private
183
+
184
+ def query_str(options = {})
185
+ keys = options[:allowed_query_params_keys] || []
186
+ query_params = options[:query_params] || {}
187
+ query_str = if query_params.empty?
188
+ ''
189
+ else
190
+ format_query_params(query_params, keys).collect{|k,v| "#{k}=#{URI.escape v}"}.join('&')
191
+ end
192
+ return query_str.empty? ? '' : "?#{query_str}"
193
+ end
194
+
195
+ # format param names and return the query hash
196
+ def format_query_params(params = {}, keys = [])
197
+ keys.inject([]){|m, v| v.to_sym; m}
198
+ query_params = params.select{|k,v| keys.include?(k.to_sym)}
199
+ cleaned = query_params.inject({}) do |p,(k,v)|
200
+ key = k.to_s.gsub('_', '').to_sym
201
+ value = v.to_s
202
+ p[key] = value
203
+ p
204
+ end
205
+ return cleaned.reject{|k,v| v.empty?}
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,5 @@
1
+ module NolijWeb
2
+ class Version
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
data/lib/nolij_web.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rest_client'
2
+ require 'uri'
3
+ require 'nolij_web/connection'
4
+ require 'nolij_web/handler'
5
+ require "nolij_web/version"
6
+ require 'nolij_web/authentication_error'
7
+ require 'nolij_web/attribute_missing_error'
8
+ require 'nolij_web/connection_configuration_error'
9
+
10
+ # See README for installation and usage instructions
11
+ module NolijWeb
12
+ # Nothing to see here
13
+ end
data/nolij_web.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nolij_web/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nolij_web"
8
+ spec.version = NolijWeb::Version::VERSION
9
+ spec.authors = ["Shannon Henderson"]
10
+ spec.email = ["shenders@reed.edu"]
11
+ spec.description = %q{A Ruby wrapper for the Nolijweb API}
12
+ spec.summary = %q{Interact with Nolijweb's REST API}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.extra_rdoc_files = spec.files.select { |p| p =~ /^README/ } << 'LICENSE.txt'
22
+ spec.rdoc_options = %w[--line-numbers --inline-source --main README.rdoc]
23
+
24
+ spec.add_runtime_dependency 'rest-client', '~> 1.6'
25
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6'
26
+
27
+ spec.add_development_dependency 'minitest', '~> 5.0.0'
28
+ spec.add_development_dependency 'bundler', '~> 1.3'
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_development_dependency 'webmock', '~>1.13'
31
+
32
+ spec.required_ruby_version = '>= 1.9.3'
33
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe NolijWeb::AttributeMissingError do
4
+ it "should subclass StandardError" do
5
+ NolijWeb::AttributeMissingError.ancestors.must_include(StandardError)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe NolijWeb::AuthenticationError do
4
+ it "should subclass StandardError" do
5
+ NolijWeb::AuthenticationError.ancestors.must_include(StandardError)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe NolijWeb::ConnectionConfigurationError do
4
+ it "should subclass StandardError" do
5
+ NolijWeb::ConnectionConfigurationError.ancestors.must_include(StandardError)
6
+ end
7
+ end