nolij_web 1.0.0

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