qa 3.1.0 → 4.0.0.rc1

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