qa 3.1.0 → 4.0.0.rc1

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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +16 -548
  3. data/app/controllers/qa/linked_data_terms_controller.rb +64 -42
  4. data/app/controllers/qa/terms_controller.rb +14 -6
  5. data/app/models/qa/iri_template/url_config.rb +47 -0
  6. data/app/models/qa/iri_template/variable_map.rb +62 -0
  7. data/app/models/qa/linked_data/config/context_map.rb +77 -0
  8. data/app/models/qa/linked_data/config/context_property_map.rb +144 -0
  9. data/app/models/qa/linked_data/config/helper.rb +34 -0
  10. data/app/services/qa/iri_template_service.rb +31 -0
  11. data/{lib/qa/authorities → app/services/qa}/linked_data/authority_service.rb +3 -4
  12. data/app/services/qa/linked_data/authority_url_service.rb +48 -0
  13. data/app/services/qa/linked_data/deep_sort_service.rb +238 -0
  14. data/app/services/qa/linked_data/graph_service.rb +106 -0
  15. data/app/services/qa/linked_data/language_service.rb +30 -0
  16. data/app/services/qa/linked_data/language_sort_service.rb +81 -0
  17. data/app/services/qa/linked_data/mapper/context_mapper_service.rb +59 -0
  18. data/app/services/qa/linked_data/mapper/graph_mapper_service.rb +40 -0
  19. data/app/services/qa/linked_data/mapper/search_results_mapper_service.rb +70 -0
  20. data/config/authorities/linked_data/loc.json +5 -2
  21. data/config/authorities/linked_data/oclc_fast.json +3 -2
  22. data/config/initializers/linked_data_authorities.rb +1 -1
  23. data/config/locales/qa.en.yml +9 -0
  24. data/lib/generators/qa/install/templates/config/initializers/qa.rb +4 -0
  25. data/lib/qa.rb +8 -0
  26. data/lib/qa/authorities/assign_fast/generic_authority.rb +1 -1
  27. data/lib/qa/authorities/base.rb +0 -11
  28. data/lib/qa/authorities/crossref/generic_authority.rb +1 -1
  29. data/lib/qa/authorities/geonames.rb +1 -1
  30. data/lib/qa/authorities/getty/aat.rb +7 -2
  31. data/lib/qa/authorities/getty/tgn.rb +7 -2
  32. data/lib/qa/authorities/getty/ulan.rb +7 -2
  33. data/lib/qa/authorities/linked_data.rb +0 -1
  34. data/lib/qa/authorities/linked_data/config.rb +29 -28
  35. data/lib/qa/authorities/linked_data/config/search_config.rb +21 -79
  36. data/lib/qa/authorities/linked_data/config/term_config.rb +7 -77
  37. data/lib/qa/authorities/linked_data/find_term.rb +25 -17
  38. data/lib/qa/authorities/linked_data/generic_authority.rb +6 -5
  39. data/lib/qa/authorities/linked_data/rdf_helper.rb +6 -73
  40. data/lib/qa/authorities/linked_data/search_query.rb +54 -101
  41. data/lib/qa/authorities/loc/generic_authority.rb +4 -4
  42. data/lib/qa/authorities/web_service_base.rb +1 -8
  43. data/lib/qa/configuration.rb +7 -0
  44. data/lib/qa/version.rb +1 -1
  45. data/lib/tasks/mesh.rake +19 -18
  46. data/spec/controllers/linked_data_terms_controller_spec.rb +51 -1
  47. data/spec/controllers/terms_controller_spec.rb +15 -15
  48. data/spec/fixtures/authorities/linked_data/lod_encoding_config.json +2 -1
  49. data/spec/fixtures/authorities/linked_data/lod_full_config.json +56 -2
  50. data/spec/fixtures/authorities/linked_data/lod_full_config_1_0.json +164 -0
  51. data/spec/fixtures/authorities/linked_data/lod_lang_defaults.json +5 -4
  52. data/spec/fixtures/authorities/linked_data/lod_lang_multi_defaults.json +3 -2
  53. data/spec/fixtures/authorities/linked_data/lod_lang_no_defaults.json +3 -2
  54. data/spec/fixtures/authorities/linked_data/lod_lang_param.json +3 -2
  55. data/spec/fixtures/authorities/linked_data/lod_min_config.json +3 -2
  56. data/spec/fixtures/authorities/linked_data/lod_search_only_config.json +2 -1
  57. data/spec/fixtures/authorities/linked_data/lod_sort.json +2 -1
  58. data/spec/fixtures/authorities/linked_data/lod_term_id_param_config.json +2 -1
  59. data/spec/fixtures/authorities/linked_data/lod_term_only_config.json +2 -1
  60. data/spec/fixtures/authorities/linked_data/lod_term_uri_param_config.json +2 -1
  61. data/spec/fixtures/getty-error-response.txt +10 -0
  62. data/spec/fixtures/lod_2_ranked_2_unranked.nt +17 -0
  63. data/spec/fixtures/lod_3_ranked_varying_preds.nt +16 -0
  64. data/spec/fixtures/lod_lang_search_filtering.nt +11 -0
  65. data/spec/fixtures/lod_search_with_blanknode_subjects.nt +18 -0
  66. data/spec/fixtures/lod_term_with_blanknode_objects.nt +8 -0
  67. data/spec/lib/authorities/assign_fast_spec.rb +1 -0
  68. data/spec/lib/authorities/getty/aat_spec.rb +14 -2
  69. data/spec/lib/authorities/getty/tgn_spec.rb +14 -2
  70. data/spec/lib/authorities/getty/ulan_spec.rb +14 -2
  71. data/spec/lib/authorities/linked_data/authority_service_spec.rb +2 -1
  72. data/spec/lib/authorities/linked_data/config_spec.rb +284 -5
  73. data/spec/lib/authorities/linked_data/find_term_spec.rb +3 -1
  74. data/spec/lib/authorities/linked_data/generic_authority_spec.rb +92 -42
  75. data/spec/lib/authorities/linked_data/search_config_spec.rb +67 -160
  76. data/spec/lib/authorities/linked_data/search_query_spec.rb +3 -127
  77. data/spec/lib/authorities/linked_data/term_config_spec.rb +6 -134
  78. data/spec/lib/authorities/loc_spec.rb +9 -9
  79. data/spec/lib/configuration_spec.rb +20 -7
  80. data/spec/lib/tasks/mesh.rake_spec.rb +2 -2
  81. data/spec/models/iri_template/url_config_spec.rb +102 -0
  82. data/spec/models/iri_template/variable_map_spec.rb +105 -0
  83. data/spec/models/linked_data/config/context_map_spec.rb +148 -0
  84. data/spec/models/linked_data/config/context_property_map_spec.rb +286 -0
  85. data/spec/services/iri_template_service_spec.rb +69 -0
  86. data/spec/services/linked_data/authority_url_service_spec.rb +107 -0
  87. data/spec/services/linked_data/deep_sort_service_spec.rb +260 -0
  88. data/spec/services/linked_data/graph_service_spec.rb +232 -0
  89. data/spec/services/linked_data/language_service_spec.rb +66 -0
  90. data/spec/services/linked_data/language_sort_service_spec.rb +58 -0
  91. data/spec/services/linked_data/mapper/context_mapper_service_spec.rb +137 -0
  92. data/spec/services/linked_data/mapper/graph_mapper_service_spec.rb +110 -0
  93. data/spec/services/linked_data/mapper/search_results_mapper_service_spec.rb +109 -0
  94. data/spec/spec_helper.rb +10 -2
  95. metadata +81 -11
@@ -18,36 +18,39 @@ class Qa::LinkedDataTermsController < ::ApplicationController
18
18
 
19
19
  # Return a list of supported authority names
20
20
  # get "/list/linked_data/authorities"
21
- # @see Qa::Authorities::LinkedData::AuthorityService#authority_names
21
+ # @see Qa::LinkedData::AuthorityService#authority_names
22
22
  def list
23
- render json: Qa::Authorities::LinkedData::AuthorityService.authority_names.to_json
23
+ render json: Qa::LinkedData::AuthorityService.authority_names.to_json
24
24
  end
25
25
 
26
26
  # Reload authority configurations
27
27
  # get "/reload/linked_data/authorities?auth_token=YOUR_AUTH_TOKEN_DEFINED_HERE"
28
- # @see Qa::Authorities::LinkedData::AuthorityService#load_authorities
28
+ # @see Qa::LinkedData::AuthorityService#load_authorities
29
29
  def reload
30
- Qa::Authorities::LinkedData::AuthorityService.load_authorities
30
+ Qa::LinkedData::AuthorityService.load_authorities
31
31
  list
32
32
  end
33
33
 
34
34
  # Return a list of terms based on a query
35
35
  # get "/search/linked_data/:vocab(/:subauthority)"
36
36
  # @see Qa::Authorities::LinkedData::SearchQuery#search
37
- def search
38
- terms = @authority.search(query, subauth: subauthority, language: language, replacements: replacement_params)
37
+ def search # rubocop:disable Metrics/MethodLength
38
+ terms = @authority.search(query, subauth: subauthority, language: language, replacements: replacement_params, context: context?)
39
39
  cors_allow_origin_header(response)
40
40
  render json: terms
41
41
  rescue Qa::ServiceUnavailable
42
- logger.warn "Service Unavailable - Search query #{query} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
43
- head :service_unavailable
42
+ msg = "Service Unavailable - Search query #{query} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
43
+ logger.warn msg
44
+ render json: { errors: msg }, status: :service_unavailable
44
45
  rescue Qa::ServiceError
45
- logger.warn "Internal Server Error - Search query #{query} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
46
- head :internal_server_error
46
+ msg = "Internal Server Error - Search query #{query} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
47
+ logger.warn msg
48
+ render json: { errors: msg }, status: :internal_server_error
47
49
  rescue RDF::FormatError
48
- logger.warn "RDF Format Error - Results from search query #{query} for#{subauth_warn_msg} authority #{vocab_param} " \
50
+ msg = "RDF Format Error - Results from search query #{query} for#{subauth_warn_msg} authority #{vocab_param} " \
49
51
  "was not identified as a valid RDF format. You may need to include the linkeddata gem."
50
- head :internal_server_error
52
+ logger.warn msg
53
+ render json: { errors: msg }, status: :internal_server_error
51
54
  end
52
55
 
53
56
  # Return all the information for a given term given an id or URI
@@ -60,18 +63,22 @@ class Qa::LinkedDataTermsController < ::ApplicationController
60
63
  content_type = jsonld? ? 'application/ld+json' : 'application/json'
61
64
  render json: term, content_type: content_type
62
65
  rescue Qa::TermNotFound
63
- logger.warn "Term Not Found - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
64
- head :not_found
66
+ msg = "Term Not Found - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
67
+ logger.warn msg
68
+ render json: { errors: msg }, status: :not_found
65
69
  rescue Qa::ServiceUnavailable
66
- logger.warn "Service Unavailable - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
67
- head :service_unavailable
70
+ msg = "Service Unavailable - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
71
+ logger.warn msg
72
+ render json: { errors: msg }, status: :service_unavailable
68
73
  rescue Qa::ServiceError
69
- logger.warn "Internal Server Error - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
70
- head :internal_server_error
74
+ msg = "Internal Server Error - Fetch term #{id} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
75
+ logger.warn msg
76
+ render json: { errors: msg }, status: :internal_server_error
71
77
  rescue RDF::FormatError
72
- logger.warn "RDF Format Error - Results from fetch term #{id} for#{subauth_warn_msg} authority #{vocab_param} " \
78
+ msg = "RDF Format Error - Results from fetch term #{id} for#{subauth_warn_msg} authority #{vocab_param} " \
73
79
  "was not identified as a valid RDF format. You may need to include the linkeddata gem."
74
- head :internal_server_error
80
+ logger.warn msg
81
+ render json: { errors: msg }, status: :internal_server_error
75
82
  end
76
83
 
77
84
  # Return all the information for a given term given a URI
@@ -83,50 +90,58 @@ class Qa::LinkedDataTermsController < ::ApplicationController
83
90
  content_type = jsonld? ? 'application/ld+json' : 'application/json'
84
91
  render json: term, content_type: content_type
85
92
  rescue Qa::TermNotFound
86
- logger.warn "Term Not Found - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
87
- head :not_found
93
+ msg = "Term Not Found - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
94
+ logger.warn msg
95
+ render json: { errors: msg }, status: :not_found
88
96
  rescue Qa::ServiceUnavailable
89
- logger.warn "Service Unavailable - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
90
- head :service_unavailable
97
+ msg = "Service Unavailable - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
98
+ logger.warn msg
99
+ render json: { errors: msg }, status: :service_unavailable
91
100
  rescue Qa::ServiceError
92
- logger.warn "Internal Server Error - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
93
- head :internal_server_error
101
+ msg = "Internal Server Error - Fetch term #{uri} unsuccessful for#{subauth_warn_msg} authority #{vocab_param}"
102
+ logger.warn msg
103
+ render json: { errors: msg }, status: :internal_server_error
94
104
  rescue RDF::FormatError
95
- logger.warn "RDF Format Error - Results from fetch term #{uri} for#{subauth_warn_msg} authority #{vocab_param} " \
105
+ msg = "RDF Format Error - Results from fetch term #{uri} for#{subauth_warn_msg} authority #{vocab_param} " \
96
106
  "was not identified as a valid RDF format. You may need to include the linkeddata gem."
97
- head :internal_server_error
107
+ logger.warn msg
108
+ render json: { errors: msg }, status: :internal_server_error
98
109
  end
99
110
 
100
111
  private
101
112
 
102
113
  def check_authority
103
114
  if params[:vocab].nil? || !params[:vocab].size.positive? # rubocop:disable Style/GuardClause
104
- logger.warn "Required param 'vocab' is missing or empty"
105
- head :bad_request
115
+ msg = "Required param 'vocab' is missing or empty"
116
+ logger.warn msg
117
+ render json: { errors: msg }, status: :bad_request
106
118
  end
107
119
  end
108
120
 
109
121
  def check_search_subauthority
110
122
  return if subauthority.nil?
111
123
  unless @authority.search_subauthority?(subauthority) # rubocop:disable Style/GuardClause
112
- logger.warn "Unable to initialize linked data search sub-authority '#{subauthority}' for authority '#{vocab_param}'"
113
- head :bad_request
124
+ msg = "Unable to initialize linked data search sub-authority '#{subauthority}' for authority '#{vocab_param}'"
125
+ logger.warn msg
126
+ render json: { errors: msg }, status: :bad_request
114
127
  end
115
128
  end
116
129
 
117
130
  def check_show_subauthority
118
131
  return if subauthority.nil?
119
132
  unless @authority.term_subauthority?(subauthority) # rubocop:disable Style/GuardClause
120
- logger.warn "Unable to initialize linked data term sub-authority '#{subauthority}' for authority '#{vocab_param}'"
121
- head :bad_request
133
+ msg = "Unable to initialize linked data term sub-authority '#{subauthority}' for authority '#{vocab_param}'"
134
+ logger.warn msg
135
+ render json: { errors: msg }, status: :bad_request
122
136
  end
123
137
  end
124
138
 
125
139
  def init_authority
126
140
  @authority = Qa::Authorities::LinkedData::GenericAuthority.new(vocab_param)
127
141
  rescue Qa::InvalidLinkedDataAuthority => e
128
- logger.warn e.message
129
- head :bad_request
142
+ msg = e.message
143
+ logger.warn msg
144
+ render json: { errors: msg }, status: :bad_request
130
145
  end
131
146
 
132
147
  def vocab_param
@@ -146,8 +161,9 @@ class Qa::LinkedDataTermsController < ::ApplicationController
146
161
  end
147
162
 
148
163
  def missing_required_param(action_name, param_name)
149
- logger.warn "Required #{action_name} param '#{param_name}' is missing or empty"
150
- head :bad_request
164
+ msg = "Required #{action_name} param '#{param_name}' is missing or empty"
165
+ logger.warn msg
166
+ render json: { errors: msg }, status: :bad_request
151
167
  end
152
168
 
153
169
  # converts wildcards into URL-encoded characters
@@ -176,7 +192,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
176
192
  end
177
193
 
178
194
  def subauth_warn_msg
179
- subauthority.nil? ? "" : " sub-authority #{subauthority} in"
195
+ subauthority.blank? ? "" : " sub-authority #{subauthority} in"
180
196
  end
181
197
 
182
198
  def format
@@ -186,15 +202,21 @@ class Qa::LinkedDataTermsController < ::ApplicationController
186
202
  end
187
203
 
188
204
  def jsonld?
189
- format == 'jsonld'
205
+ format.casecmp('jsonld').zero?
206
+ end
207
+
208
+ def context?
209
+ context = params.fetch(:context, 'false')
210
+ context.casecmp('true').zero?
190
211
  end
191
212
 
192
213
  def validate_auth_reload_token
193
214
  token = params.key?(:auth_token) ? params[:auth_token] : nil
194
215
  valid = Qa.config.valid_authority_reload_token?(token)
195
216
  return true if valid
196
- logger.warn "FAIL: unable to reload authorities; error_msg: Invalid token (#{token}) does not match expected token."
197
- head :unauthorized
217
+ msg = "FAIL: unable to reload authorities; error_msg: Invalid token (#{token}) does not match expected token."
218
+ logger.warn msg
219
+ render json: { errors: msg }, status: :unauthorized
198
220
  false
199
221
  end
200
222
  end
@@ -34,15 +34,19 @@ class Qa::TermsController < ::ApplicationController
34
34
  end
35
35
 
36
36
  def check_vocab_param
37
- head :not_found unless params[:vocab].present?
37
+ return if params[:vocab].present?
38
+ msg = "Required param 'vocab' is missing or empty"
39
+ logger.warn msg
40
+ render json: { errors: msg }, status: :bad_request
38
41
  end
39
42
 
40
43
  def init_authority # rubocop:disable Metrics/MethodLength
41
44
  begin
42
45
  mod = authority_class.camelize.constantize
43
46
  rescue NameError
44
- logger.warn "Unable to initialize authority #{authority_class}"
45
- head :not_found
47
+ msg = "Unable to initialize authority #{authority_class}"
48
+ logger.warn msg
49
+ render json: { errors: msg }, status: :bad_request
46
50
  return
47
51
  end
48
52
  begin
@@ -53,13 +57,17 @@ class Qa::TermsController < ::ApplicationController
53
57
  mod.subauthority_for(params[:subauthority])
54
58
  end
55
59
  rescue Qa::InvalidSubAuthority, Qa::MissingSubAuthority => e
56
- logger.warn e.message
57
- head :not_found
60
+ msg = e.message
61
+ logger.warn msg
62
+ render json: { errors: msg }, status: :bad_request
58
63
  end
59
64
  end
60
65
 
61
66
  def check_query_param
62
- head :not_found unless params[:q].present?
67
+ return if params[:q].present?
68
+ msg = "Required param 'q' is missing or empty"
69
+ logger.warn msg
70
+ render json: { errors: msg }, status: :bad_request
63
71
  end
64
72
 
65
73
  private
@@ -0,0 +1,47 @@
1
+ # Provide access to iri template configuration.
2
+ # See https://www.hydra-cg.com/spec/latest/core/#templated-links for information on IRI Templated Links.
3
+ # TODO: It would be good to find a more complete resource describing templated links.
4
+ module Qa
5
+ module IriTemplate
6
+ class UrlConfig
7
+ TYPE = "IriTemplate".freeze
8
+ CONTEXT = "http://www.w3.org/ns/hydra/context.jsonld".freeze
9
+ attr_reader :template # [String] the URL template with variables for substitution (required)
10
+ attr_reader :variable_representation # [String] always "BasicRepresentation" # TODO what other values are supported and what do they mean
11
+ attr_reader :mapping # [Array<Qa::IriTempalte::VariableMap>] array of maps for use with a template (required)
12
+
13
+ # @param [Hash] url_config configuration hash for the iri template
14
+ # @option url_config [String] :template the URL template with variables for substitution (required)
15
+ # @option url_config [String] :variable_representation always "BasicRepresentation" # TODO what other values are supported and what do they mean
16
+ # @option url_config [Array<Hash>] :mapping array of maps for use with a template (required)
17
+ def initialize(url_config)
18
+ @url_config = url_config
19
+ @template = Qa::LinkedData::Config::Helper.fetch_required(url_config, :template, nil)
20
+ @mapping = extract_mapping
21
+ @variable_representation = Qa::LinkedData::Config::Helper.fetch(url_config, :variable_representation, 'BasicRepresentation')
22
+ end
23
+
24
+ # Selective extract substitution variable-value pairs from the provided substitutions.
25
+ # @param [Hash, ActionController::Parameters] full set of passed in substitution values
26
+ # @return [HashWithIndifferentAccess] Only variable-value pairs for variables defined in the variable mapping.
27
+ def extract_substitutions(substitutions)
28
+ selected_substitutions = HashWithIndifferentAccess.new
29
+ mapping.each do |m|
30
+ selected_substitutions[m.variable] = substitutions[m.variable] if substitutions.key? m.variable
31
+ end
32
+ selected_substitutions
33
+ end
34
+
35
+ private
36
+
37
+ # Initialize the variable maps
38
+ # @param config [Hash] configuration holding the variable maps to be extracted
39
+ # @return [Array<IriTemplate::Map>] array of the variable maps
40
+ def extract_mapping
41
+ mapping = Qa::LinkedData::Config::Helper.fetch_required(@url_config, :mapping, nil)
42
+ raise Qa::InvalidConfiguration, "mapping must include at least one map" if mapping.empty?
43
+ mapping.collect { |m| Qa::IriTemplate::VariableMap.new(m) }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,62 @@
1
+ # Provide access to iri template variable map configuration.
2
+ # See https://www.hydra-cg.com/spec/latest/core/#templated-links for information on IRI Templated Links - Variable Mapping.
3
+ # TODO: It would be good to find a more complete resource describing templated links.
4
+
5
+ module Qa
6
+ module IriTemplate
7
+ class VariableMap
8
+ TYPE = "IriTemplateMapping".freeze
9
+ attr_reader :variable
10
+ attr_reader :default
11
+ private :default
12
+
13
+ # @param [Hash] variable_map configuration hash for the variable map
14
+ # @option variable_map [String] :variable (required) name of the variable in the template (e.g. {?query} has the name 'query')
15
+ # @option variable_map [String] :property (optional) always "hydra:freetextQuery" # TODO what other values are supported and what do they mean
16
+ # @option variable_map [Boolean] :required (required) is this variable required
17
+ # @option variable_map [String] :default (optional) value to use if a value is not provided in the request (default: '')
18
+ # @option variable_map [Boolean] :encode (optional) whether to url_encode the value (default: false)
19
+ def initialize(variable_map)
20
+ @variable = Qa::LinkedData::Config::Helper.fetch_required(variable_map, :variable, nil)
21
+ @required = Qa::LinkedData::Config::Helper.fetch_boolean(variable_map, :required, nil)
22
+ @default = Qa::LinkedData::Config::Helper.fetch(variable_map, :default, '').to_s
23
+ @encode = Qa::LinkedData::Config::Helper.fetch_boolean(variable_map, :encode, false)
24
+ @property = Qa::LinkedData::Config::Helper.fetch(variable_map, :property, 'hydra:freetextQuery')
25
+ end
26
+
27
+ # Value to use in substitution, using default if one isn't passed in. Use when template url specifies variable as {var_name}.
28
+ # @param [Object] value to use if it exists
29
+ # @return the value to use (e.g. 'fr')
30
+ def simple_value(sub_value = nil)
31
+ raise Qa::IriTemplate::MissingParameter, "#{variable} is required, but missing" if sub_value.blank? && required?
32
+ return default if sub_value.blank?
33
+ sub_value = sub_value.to_s
34
+ sub_value = ERB::Util.url_encode(sub_value).gsub(".", "%2E") if encode?
35
+ sub_value
36
+ end
37
+
38
+ # Parameter and value to use in substitution, using default is one isn't passed in. Use when template url specifies variable as {?var_name}.
39
+ # @param [Object] value to use if it exists
40
+ # @return the parameter and value to use (e.g. 'language=fr')
41
+ def parameter_value(sub_value = nil)
42
+ simple_value = simple_value(sub_value)
43
+ return '' if simple_value.blank?
44
+ "#{variable}=#{simple_value}"
45
+ end
46
+
47
+ private
48
+
49
+ # Is this variable required?
50
+ # @return true if required; otherwise, false
51
+ def required?
52
+ @required
53
+ end
54
+
55
+ # Should the variable's value be encoded?
56
+ # @return true if should encode; otherwise, false
57
+ def encode?
58
+ @encode
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,77 @@
1
+ # Defines the external authority predicates used to extract additional context from the graph.
2
+ module Qa
3
+ module LinkedData
4
+ module Config
5
+ class ContextMap
6
+ attr_reader :properties # [Array<Qa::LinkedData::Config::ContextPropertyMap>] set of property map models
7
+
8
+ attr_reader :context_map, :groups, :prefixes
9
+ private :context_map, :groups, :prefixes
10
+
11
+ # @param [Hash] context_map that defines groups and properties for additional context to display during the selection process
12
+ # @option context_map [Hash] :groups (optional) predefine group ids and labels to be used in the properties section to group properties
13
+ # @option groups [Hash] key=group_id; value=[Hash] with group_label_i18n and/or group_label_default
14
+ # @option context_map [Array<Hash>] :properties (optional) property maps defining how to get and display the additional context (if none, context will not be added)
15
+ # @param [Hash] shortcut names for URI prefixes with key = part of predicate that is the same for all terms (e.g. { "madsrdf": "http://www.loc.gov/mads/rdf/v1#" })
16
+ # @example context_map example
17
+ # {
18
+ # "groups": {
19
+ # "dates": {
20
+ # "group_label_i18n": "qa.linked_data.authority.locnames_ld4l_cache.dates",
21
+ # "group_label_default": "Dates"
22
+ # }
23
+ # },
24
+ # "properties": [
25
+ # {
26
+ # "property_label_i18n": "qa.linked_data.authority.locgenres_ld4l_cache.authoritative_label",
27
+ # "property_label_default": "Authoritative Label",
28
+ # "ldpath": "madsrdf:authoritativeLabel",
29
+ # "selectable": true,
30
+ # "drillable": false
31
+ # },
32
+ # {
33
+ # "group_id": "dates",
34
+ # "property_label_i18n": "qa.linked_data.authority.locnames_ld4l_cache.birth_date",
35
+ # "property_label_default": "Birth",
36
+ # "ldpath": "madsrdf:identifiesRWO/madsrdf:birthDate/schema:label",
37
+ # "selectable": false,
38
+ # "drillable": false
39
+ # }
40
+ # ]
41
+ # }
42
+ def initialize(context_map = {}, prefixes = {})
43
+ @context_map = context_map
44
+ @prefixes = prefixes
45
+ extract_groups
46
+ extract_properties
47
+ end
48
+
49
+ def group_label(group_id)
50
+ groups[group_id]
51
+ end
52
+
53
+ private
54
+
55
+ def extract_properties
56
+ @properties = []
57
+ properties_map = Qa::LinkedData::Config::Helper.fetch(context_map, :properties, {})
58
+ properties_map.each { |property_map| @properties << ContextPropertyMap.new(property_map, prefixes) }
59
+ end
60
+
61
+ def extract_groups
62
+ @groups = {}
63
+ groups_map = Qa::LinkedData::Config::Helper.fetch(context_map, :groups, {})
64
+ groups_map.each { |group_id, group_map| add_group(group_id, group_map) }
65
+ end
66
+
67
+ def add_group(group_id, group_map)
68
+ return if groups.key? group_id
69
+ i18n_key = Qa::LinkedData::Config::Helper.fetch(group_map, :group_label_i18n, nil)
70
+ default = Qa::LinkedData::Config::Helper.fetch(group_map, :group_label_default, nil)
71
+ return groups[group_id] = I18n.t(i18n_key, default: default) if i18n_key.present?
72
+ groups[group_id] = default
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,144 @@
1
+ # Defines the external authority predicates used to extract additional context from the graph.
2
+ require 'ldpath'
3
+
4
+ module Qa
5
+ module LinkedData
6
+ module Config
7
+ class ContextPropertyMap
8
+ VALUE_ON_ERROR = [].freeze
9
+
10
+ attr_reader :group_id, # id that identifies which group the property should be in
11
+ :label
12
+
13
+ attr_reader :property_map, :ldpath, :expansion_label_ldpath, :expansion_id_ldpath, :prefixes
14
+ private :property_map, :ldpath, :expansion_label_ldpath, :expansion_id_ldpath, :prefixes
15
+
16
+ # @param [Hash] property_map defining information to return to provide context
17
+ # @option property_map [String] :group_id (optional) default label to use for a property (default: no label)
18
+ # @option property_map [String] :property_label_i18n (optional) i18n key to use to get the label for a property (default: property_label_default OR no label if neither are defined)
19
+ # @option property_map [String] :property_label_default (optional) default label to use for a property (default: no label)
20
+ # @option property_map [String] :ldpath (required) identifies the values to extract from the graph (based on http://marmotta.apache.org/ldpath/language.html)
21
+ # @option property_map [Boolean] :selectable (optional) if true, this property can selected as the value (default: false)
22
+ # @option property_map [Boolean] :drillable (optional) if true, the label for this property can be used to execute a second query allowing navi (default: false)
23
+ # @param [Hash] shortcut names for URI prefixes with key = part of predicate that is the same for all terms (e.g. { "madsrdf": "http://www.loc.gov/mads/rdf/v1#" })
24
+ # @example property_map example
25
+ # {
26
+ # "group_id": "dates",
27
+ # "property_label_i18n": "qa.linked_data.authority.locnames_ld4l_cache.birth_date",
28
+ # "property_label_default": "Birth",
29
+ # "ldpath": "madsrdf:identifiesRWO/madsrdf:birthDate/schema:label",
30
+ # "selectable": false,
31
+ # "drillable": false
32
+ # }
33
+ def initialize(property_map = {}, prefixes = {})
34
+ @property_map = property_map
35
+ @group_id = Qa::LinkedData::Config::Helper.fetch_symbol(property_map, :group_id, nil)
36
+ @label = extract_label
37
+ @ldpath = Qa::LinkedData::Config::Helper.fetch_required(property_map, :ldpath, false)
38
+ @selectable = Qa::LinkedData::Config::Helper.fetch_boolean(property_map, :selectable, false)
39
+ @drillable = Qa::LinkedData::Config::Helper.fetch_boolean(property_map, :drillable, false)
40
+ @expansion_label_ldpath = Qa::LinkedData::Config::Helper.fetch(property_map, :expansion_label_ldpath, nil)
41
+ @expansion_id_ldpath = Qa::LinkedData::Config::Helper.fetch(property_map, :expansion_id_ldpath, nil)
42
+ @prefixes = prefixes
43
+ end
44
+
45
+ # Can this property be the selected value?
46
+ # @return [Boolean] true if this property's value can be selected; otherwise, false
47
+ def selectable?
48
+ @selectable
49
+ end
50
+
51
+ # Can this property be used as a new query
52
+ # @return [Boolean] true if this property's value can be used to drill up/down to another level; otherwise, false
53
+ def drillable?
54
+ @drillable
55
+ end
56
+
57
+ def group?
58
+ group_id.present?
59
+ end
60
+
61
+ # Should this URI value be expanded to include its label?
62
+ # @return [Boolean] true if this property's value is expected to be a URI and its label should be included in the value; otherwise, false
63
+ def expand_uri?
64
+ expansion_label_ldpath.present?
65
+ end
66
+
67
+ # Values of this property for a specfic subject URI
68
+ # @return [Array<String>] values for this property
69
+ def values(graph, subject_uri)
70
+ ldpath_evaluate(basic_program, graph, subject_uri)
71
+ end
72
+
73
+ # Values of this property for a specfic subject URI with URI values expanded to include id and label.
74
+ # @return [Array<Hash>] expanded values for this property
75
+ # @example returned values
76
+ # [{
77
+ # uri: "http://id.loc.gov/authorities/genreForms/gf2014026551",
78
+ # id: "gf2014026551",
79
+ # label: "Space operas"
80
+ # }]
81
+ def expanded_values(graph, subject_uri)
82
+ values = values(graph, subject_uri)
83
+ return values unless expand_uri?
84
+ return values unless values.respond_to? :map!
85
+ values.map! do |uri|
86
+ { uri: uri, id: expansion_id(graph, uri), label: expansion_label(graph, uri) }
87
+ end
88
+ values
89
+ end
90
+
91
+ private
92
+
93
+ def extract_label
94
+ i18n_key = Qa::LinkedData::Config::Helper.fetch(property_map, :property_label_i18n, nil)
95
+ default = Qa::LinkedData::Config::Helper.fetch(property_map, :property_label_default, nil)
96
+ return I18n.t(i18n_key, default: default) if i18n_key.present?
97
+ default
98
+ end
99
+
100
+ def basic_program
101
+ @basic_program ||= ldpath_program(ldpath)
102
+ end
103
+
104
+ def expansion_label_program
105
+ @expansion_label_program ||= ldpath_program(expansion_label_ldpath)
106
+ end
107
+
108
+ def expansion_id_program
109
+ @expansion_id_program ||= ldpath_program(expansion_id_ldpath)
110
+ end
111
+
112
+ def ldpath_program(ldpath)
113
+ program_code = ""
114
+ prefixes.each { |key, url| program_code << "@prefix #{key} : <#{url}> \;\n" }
115
+ program_code << "property = #{ldpath} \;"
116
+ Ldpath::Program.parse program_code
117
+ rescue => e
118
+ Rails.logger.warn("WARNING: #{I18n.t('qa.linked_data.ldpath.parse_logger_error')} (ldpath='#{ldpath}')\n cause: #{e.message}")
119
+ raise StandardError, I18n.t('qa.linked_data.ldpath.parse_error')
120
+ end
121
+
122
+ def ldpath_evaluate(program, graph, subject_uri)
123
+ return VALUE_ON_ERROR if program.blank?
124
+ output = program.evaluate subject_uri, graph
125
+ output.present? ? output['property'].uniq : nil
126
+ rescue => e
127
+ Rails.logger.warn("WARNING: #{I18n.t('qa.linked_data.ldpath.evaluate_logger_error')} (ldpath='#{ldpath}')\n cause: #{e.message}")
128
+ raise StandardError, I18n.t('qa.linked_data.ldpath.evaluate_error')
129
+ end
130
+
131
+ def expansion_label(graph, uri)
132
+ label = ldpath_evaluate(expansion_label_program, graph, RDF::URI(uri))
133
+ label.size == 1 ? label.first : label
134
+ end
135
+
136
+ def expansion_id(graph, uri)
137
+ return uri if expansion_id_ldpath.blank?
138
+ id = ldpath_evaluate(expansion_id_program, graph, RDF::URI(uri))
139
+ id.size == 1 ? id.first : id
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end