openbel-api 0.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gemspec +65 -0
  3. data/CHANGELOG.md +22 -0
  4. data/INSTALL.md +19 -0
  5. data/INSTALL_RUBY.md +107 -0
  6. data/LICENSE +191 -0
  7. data/README.md +208 -0
  8. data/app/openbel/api/app.rb +83 -0
  9. data/app/openbel/api/config.rb +45 -0
  10. data/app/openbel/api/config.ru +3 -0
  11. data/app/openbel/api/helpers/pager.rb +109 -0
  12. data/app/openbel/api/middleware/auth.rb +112 -0
  13. data/app/openbel/api/resources/adapters/basic_json.rb +52 -0
  14. data/app/openbel/api/resources/annotation.rb +141 -0
  15. data/app/openbel/api/resources/base.rb +16 -0
  16. data/app/openbel/api/resources/completion.rb +89 -0
  17. data/app/openbel/api/resources/evidence.rb +115 -0
  18. data/app/openbel/api/resources/evidence_transform.rb +143 -0
  19. data/app/openbel/api/resources/function.rb +98 -0
  20. data/app/openbel/api/resources/match_result.rb +79 -0
  21. data/app/openbel/api/resources/namespace.rb +174 -0
  22. data/app/openbel/api/routes/annotations.rb +168 -0
  23. data/app/openbel/api/routes/authenticate.rb +108 -0
  24. data/app/openbel/api/routes/base.rb +326 -0
  25. data/app/openbel/api/routes/datasets.rb +519 -0
  26. data/app/openbel/api/routes/evidence.rb +330 -0
  27. data/app/openbel/api/routes/expressions.rb +560 -0
  28. data/app/openbel/api/routes/functions.rb +41 -0
  29. data/app/openbel/api/routes/namespaces.rb +382 -0
  30. data/app/openbel/api/routes/root.rb +39 -0
  31. data/app/openbel/api/schemas.rb +34 -0
  32. data/app/openbel/api/schemas/annotation_collection.schema.json +20 -0
  33. data/app/openbel/api/schemas/annotation_resource.schema.json +36 -0
  34. data/app/openbel/api/schemas/annotation_value_collection.schema.json +21 -0
  35. data/app/openbel/api/schemas/annotation_value_resource.schema.json +35 -0
  36. data/app/openbel/api/schemas/completion_collection.schema.json +21 -0
  37. data/app/openbel/api/schemas/completion_resource.schema.json +146 -0
  38. data/app/openbel/api/schemas/evidence.schema.json +198 -0
  39. data/app/openbel/api/schemas/evidence_collection.schema.json +98 -0
  40. data/app/openbel/api/schemas/evidence_resource.schema.json +29 -0
  41. data/app/openbel/api/schemas/namespace_value_collection.schema.json +21 -0
  42. data/app/openbel/api/schemas/namespace_value_resource.schema.json +43 -0
  43. data/app/openbel/api/util.rb +11 -0
  44. data/bin/openbel-api +78 -0
  45. data/bin/openbel-config +46 -0
  46. data/config/async_evidence.rb +12 -0
  47. data/config/async_jena.rb +14 -0
  48. data/config/config.yml +31 -0
  49. data/config/server_config.rb +184 -0
  50. data/lib/openbel/api/cache/cache.rb +30 -0
  51. data/lib/openbel/api/config/config.rb +33 -0
  52. data/lib/openbel/api/evidence/api.rb +39 -0
  53. data/lib/openbel/api/evidence/facet_api.rb +18 -0
  54. data/lib/openbel/api/evidence/facet_filter.rb +83 -0
  55. data/lib/openbel/api/evidence/mongo.rb +247 -0
  56. data/lib/openbel/api/evidence/mongo_facet.rb +105 -0
  57. data/lib/openbel/api/helpers/dependency_graph.rb +52 -0
  58. data/lib/openbel/api/model/rdf_resource.rb +74 -0
  59. data/lib/openbel/api/plugin/cache/kyotocabinet.rb +85 -0
  60. data/lib/openbel/api/plugin/configure_plugins.rb +97 -0
  61. data/lib/openbel/api/plugin/evidence/evidence.rb +58 -0
  62. data/lib/openbel/api/plugin/plugin.rb +99 -0
  63. data/lib/openbel/api/plugin/plugin_manager.rb +20 -0
  64. data/lib/openbel/api/plugin/plugin_repository.rb +60 -0
  65. data/lib/openbel/api/storage/cache_proxy.rb +74 -0
  66. data/lib/openbel/api/storage/triple_storage.rb +43 -0
  67. metadata +379 -0
@@ -0,0 +1,330 @@
1
+ require 'bel'
2
+ require 'cgi'
3
+ require 'openbel/api/evidence/mongo'
4
+ require 'openbel/api/evidence/facet_filter'
5
+ require_relative '../resources/evidence_transform'
6
+ require_relative '../helpers/pager'
7
+
8
+ module OpenBEL
9
+ module Routes
10
+
11
+ class Evidence < Base
12
+ include OpenBEL::Evidence::FacetFilter
13
+ include OpenBEL::Resource::Evidence
14
+ include OpenBEL::Helpers
15
+
16
+ def initialize(app)
17
+ super
18
+
19
+ @api = OpenBEL::Evidence::Evidence.new(
20
+ :host => OpenBEL::Settings[:evidence_store][:mongo][:host],
21
+ :port => OpenBEL::Settings[:evidence_store][:mongo][:port],
22
+ :database => OpenBEL::Settings[:evidence_store][:mongo][:database]
23
+ )
24
+
25
+ # RdfRepository using Jena
26
+ @rr = BEL::RdfRepository.plugins[:jena].create_repository(
27
+ :tdb_directory => OpenBEL::Settings[:resource_rdf][:jena][:tdb_directory]
28
+ )
29
+
30
+ # Annotations using RdfRepository
31
+ annotations = BEL::Resource::Annotations.new(@rr)
32
+
33
+ @annotation_transform = AnnotationTransform.new(annotations)
34
+ @annotation_grouping_transform = AnnotationGroupingTransform.new
35
+ end
36
+
37
+ helpers do
38
+
39
+ def stream_evidence_objects(cursor)
40
+
41
+ stream :keep_open do |response|
42
+ cursor.each do |evidence|
43
+ evidence.delete('facets')
44
+
45
+ response << render_resource(
46
+ evidence,
47
+ :evidence,
48
+ :as_array => false,
49
+ :_id => evidence['_id'].to_s
50
+ )
51
+ end
52
+ end
53
+ end
54
+
55
+ def stream_evidence_array(cursor)
56
+ stream :keep_open do |response|
57
+ current = 0
58
+
59
+ # determine true size of cursor given cursor limit/count
60
+ if cursor.limit.zero?
61
+ total = cursor.total
62
+ else
63
+ total = [cursor.limit, cursor.count].min
64
+ end
65
+
66
+ response << '['
67
+ cursor.each do |evidence|
68
+ evidence.delete('facets')
69
+
70
+ response << render_resource(
71
+ evidence,
72
+ :evidence,
73
+ :as_array => false,
74
+ :_id => evidence['_id'].to_s
75
+ )
76
+ current += 1
77
+ response << ',' if current < total
78
+ end
79
+ response << ']'
80
+ end
81
+ end
82
+
83
+ def keys_to_s_deep(hash)
84
+ hash.inject({}) do |new_hash, (key, value)|
85
+ kstr = key.to_s
86
+ if value.kind_of?(Hash)
87
+ new_hash[kstr] = keys_to_s_deep(value)
88
+ elsif value.kind_of?(Array)
89
+ new_hash[kstr] = value.map do |item|
90
+ item.kind_of?(Hash) ?
91
+ keys_to_s_deep(item) :
92
+ item
93
+ end
94
+ else
95
+ new_hash[kstr] = value
96
+ end
97
+ new_hash
98
+ end
99
+ end
100
+ end
101
+
102
+ options '/api/evidence' do
103
+ response.headers['Allow'] = 'OPTIONS,POST,GET'
104
+ status 200
105
+ end
106
+
107
+ options '/api/evidence/:id' do
108
+ response.headers['Allow'] = 'OPTIONS,GET,PUT,DELETE'
109
+ status 200
110
+ end
111
+
112
+ post '/api/evidence' do
113
+ # Validate JSON Evidence.
114
+ validate_media_type! "application/json"
115
+ evidence_obj = read_json
116
+
117
+ schema_validation = validate_schema(keys_to_s_deep(evidence_obj), :evidence)
118
+ unless schema_validation[0]
119
+ halt(
120
+ 400,
121
+ { 'Content-Type' => 'application/json' },
122
+ render_json({ :status => 400, :msg => schema_validation[1].join("\n") })
123
+ )
124
+ end
125
+
126
+ evidence = ::BEL::Model::Evidence.create(evidence_obj[:evidence])
127
+
128
+ # Standardize annotations.
129
+ @annotation_transform.transform_evidence!(evidence, base_url)
130
+
131
+ # Build facets.
132
+ facets = map_evidence_facets(evidence)
133
+ hash = evidence.to_h
134
+ hash[:bel_statement] = hash.fetch(:bel_statement, nil).to_s
135
+ hash[:facets] = facets
136
+ _id = @api.create_evidence(hash)
137
+
138
+ # Return Location information (201).
139
+ status 201
140
+ headers "Location" => "#{base_url}/api/evidence/#{_id}"
141
+ end
142
+
143
+ get '/api/evidence-stream', provides: 'application/json' do
144
+ start = (params[:start] || 0).to_i
145
+ size = (params[:size] || 0).to_i
146
+ group_as_array = as_bool(params[:group_as_array])
147
+
148
+ # check filters
149
+ filters = []
150
+ filter_params = CGI::parse(env["QUERY_STRING"])['filter']
151
+ filter_params.each do |filter|
152
+ filter = read_filter(filter)
153
+ halt 400 unless ['category', 'name', 'value'].all? { |f| filter.include? f}
154
+
155
+ if filter['category'] == 'fts' && filter['name'] == 'search'
156
+ halt 400 unless filter['value'].to_s.length > 1
157
+ end
158
+
159
+ filters << filter
160
+ end
161
+
162
+ cursor = @api.find_evidence(filters, start, size, false)[:cursor]
163
+ if group_as_array
164
+ stream_evidence_array(cursor)
165
+ else
166
+ stream_evidence_objects(cursor)
167
+ end
168
+ end
169
+
170
+ get '/api/evidence' do
171
+ start = (params[:start] || 0).to_i
172
+ size = (params[:size] || 0).to_i
173
+ faceted = as_bool(params[:faceted])
174
+ max_values_per_facet = (params[:max_values_per_facet] || 0).to_i
175
+
176
+ # check filters
177
+ filters = []
178
+ filter_params = CGI::parse(env["QUERY_STRING"])['filter']
179
+ filter_params.each do |filter|
180
+ filter = read_filter(filter)
181
+ halt 400 unless ['category', 'name', 'value'].all? { |f| filter.include? f}
182
+
183
+ if filter['category'] == 'fts' && filter['name'] == 'search'
184
+ halt 400 unless filter['value'].to_s.length > 1
185
+ end
186
+
187
+ filters << filter
188
+ end
189
+
190
+ collection_total = @api.count_evidence()
191
+ filtered_total = @api.count_evidence(filters)
192
+ page_results = @api.find_evidence(filters, start, size, faceted)
193
+ evidence = page_results[:cursor].map { |item|
194
+ item.delete('facets')
195
+ item
196
+ }.to_a
197
+ facets = page_results[:facets]
198
+
199
+ halt 404 if evidence.empty?
200
+
201
+ pager = Pager.new(start, size, filtered_total)
202
+
203
+ options = {
204
+ :start => start,
205
+ :size => size,
206
+ :filters => filter_params,
207
+ :metadata => {
208
+ :collection_paging => {
209
+ :total => collection_total,
210
+ :total_filtered => pager.total_size,
211
+ :total_pages => pager.total_pages,
212
+ :current_page => pager.current_page,
213
+ :current_page_size => evidence.size,
214
+ }
215
+ }
216
+ }
217
+
218
+ if facets
219
+ # group by category/name
220
+ hashed_values = Hash.new { |hash, key| hash[key] = [] }
221
+ facets.each { |facet|
222
+ filter = read_filter(facet['_id'])
223
+ category, name = filter.values_at('category', 'name')
224
+ next if !category || !name
225
+
226
+ key = [category.to_sym, name.to_sym]
227
+ facet_obj = {
228
+ :value => filter['value'],
229
+ :filter => facet['_id'],
230
+ :count => facet['count']
231
+ }
232
+ hashed_values[key] << facet_obj
233
+ }
234
+
235
+ if max_values_per_facet == 0
236
+ facet_hashes = hashed_values.map { |(category, name), value_objects|
237
+ {
238
+ :category => category,
239
+ :name => name,
240
+ :values => value_objects
241
+ }
242
+ }
243
+ else
244
+ facet_hashes = hashed_values.map { |(category, name), value_objects|
245
+ {
246
+ :category => category,
247
+ :name => name,
248
+ :values => value_objects.take(max_values_per_facet)
249
+ }
250
+ }
251
+ end
252
+
253
+ options[:facets] = facet_hashes
254
+ end
255
+
256
+ # pager links
257
+ options[:previous_page] = pager.previous_page
258
+ options[:next_page] = pager.next_page
259
+
260
+ render_collection(evidence, :evidence, options)
261
+ end
262
+
263
+ get '/api/evidence/:id' do
264
+ object_id = params[:id]
265
+ halt 404 unless BSON::ObjectId.legal?(object_id)
266
+
267
+ evidence = @api.find_evidence_by_id(object_id)
268
+ halt 404 unless evidence
269
+
270
+ evidence.delete('facets')
271
+
272
+ # XXX Hack to return single resource wrapped as json array
273
+ # XXX Need to better support evidence resource arrays in base.rb
274
+ render_resource(
275
+ evidence,
276
+ :evidence,
277
+ :as_array => false,
278
+ :_id => object_id
279
+ )
280
+ end
281
+
282
+ put '/api/evidence/:id' do
283
+ object_id = params[:id]
284
+ halt 404 unless BSON::ObjectId.legal?(object_id)
285
+
286
+ validate_media_type! "application/json"
287
+
288
+ ev = @api.find_evidence_by_id(object_id)
289
+ halt 404 unless ev
290
+
291
+ evidence_obj = read_json
292
+ schema_validation = validate_schema(keys_to_s_deep(evidence_obj), :evidence)
293
+ unless schema_validation[0]
294
+ halt(
295
+ 400,
296
+ { 'Content-Type' => 'application/json' },
297
+ render_json({ :status => 400, :msg => schema_validation[1].join("\n") })
298
+ )
299
+ end
300
+
301
+ # transformation
302
+ evidence = evidence_obj[:evidence]
303
+ evidence_model = ::BEL::Model::Evidence.create(evidence)
304
+ @annotation_transform.transform_evidence!(evidence_model, base_url)
305
+ facets = map_evidence_facets(evidence_model)
306
+ evidence = evidence_model.to_h
307
+ evidence[:bel_statement] = evidence.fetch(:bel_statement, nil).to_s
308
+ evidence[:facets] = facets
309
+
310
+ @api.update_evidence_by_id(object_id, evidence)
311
+
312
+ status 202
313
+ end
314
+
315
+ delete '/api/evidence/:id' do
316
+ object_id = params[:id]
317
+ halt 404 unless BSON::ObjectId.legal?(object_id)
318
+
319
+ ev = @api.find_evidence_by_id(object_id)
320
+ halt 404 unless ev
321
+
322
+ @api.delete_evidence_by_id(object_id)
323
+ status 202
324
+ end
325
+
326
+ end
327
+ end
328
+ end
329
+ # vim: ts=2 sw=2:
330
+ # encoding: utf-8
@@ -0,0 +1,560 @@
1
+ require 'cgi'
2
+ require 'bel'
3
+ require 'uri'
4
+
5
+ module OpenBEL
6
+ module Routes
7
+
8
+ class Expressions < Base
9
+
10
+ def initialize(app)
11
+ super
12
+
13
+ # RdfRepository using Jena.
14
+ @rr = BEL::RdfRepository.plugins[:jena].create_repository(
15
+ :tdb_directory => OpenBEL::Settings[:resource_rdf][:jena][:tdb_directory]
16
+ )
17
+
18
+ # Annotations using RdfRepository
19
+ @annotations = BEL::Resource::Annotations.new(@rr)
20
+ # Namespaces using RdfRepository
21
+ @namespaces = BEL::Resource::Namespaces.new(@rr)
22
+
23
+ # Resource Search using SQLite.
24
+ @search = BEL::Resource::Search.plugins[:sqlite].create_search(
25
+ :database_file => OpenBEL::Settings[:resource_search][:sqlite][:database_file]
26
+ )
27
+
28
+ @sequence_variation = SequenceVariationFunctionHasLocationPredicate.new
29
+ end
30
+
31
+ options '/api/expressions/*/completions' do
32
+ response.headers['Allow'] = 'OPTIONS,GET'
33
+ status 200
34
+ end
35
+
36
+ options '/api/expressions/*/components/?' do
37
+ response.headers['Allow'] = 'OPTIONS,GET'
38
+ status 200
39
+ end
40
+
41
+ options '/api/expressions/*/components/terms?' do
42
+ response.headers['Allow'] = 'OPTIONS,GET'
43
+ status 200
44
+ end
45
+
46
+ options '/api/expressions/*/syntax-validations/?' do
47
+ response.headers['Allow'] = 'OPTIONS,GET'
48
+ status 200
49
+ end
50
+
51
+ options '/api/expressions/*/semantic-validations/?' do
52
+ response.headers['Allow'] = 'OPTIONS,GET'
53
+ status 200
54
+ end
55
+
56
+ # options '/api/expressions/*/ortholog' do
57
+ # response.headers['Allow'] = 'OPTIONS,GET'
58
+ # status 200
59
+ # end
60
+
61
+ # options '/api/expressions/*/ortholog/:species' do
62
+ # response.headers['Allow'] = 'OPTIONS,GET'
63
+ # status 200
64
+ # end
65
+
66
+ helpers do
67
+
68
+ def normalize_relationship(relationship)
69
+ return nil unless relationship
70
+ BEL::Language::RELATIONSHIPS[relationship.to_sym]
71
+ end
72
+
73
+ def statement_components(bel_statement, flatten = false)
74
+ obj = {}
75
+ if flatten
76
+ obj[:subject] = bel_statement.subject ? bel_statement.subject.to_bel : nil
77
+ obj[:relationship] = normalize_relationship(bel_statement.relationship)
78
+ obj[:object] = bel_statement.object ? bel_statement.object.to_bel : nil
79
+ else
80
+ obj[:subject] = term_components(bel_statement.subject)
81
+ obj[:relationship] = normalize_relationship(bel_statement.relationship)
82
+ obj[:object] = term_components(bel_statement.object)
83
+ end
84
+ obj
85
+ end
86
+
87
+ def arg_components(bel_argument)
88
+ if bel_argument.respond_to? :fx
89
+ term_components(bel_argument)
90
+ elsif bel_argument.respond_to? :ns
91
+ parameter_components(bel_argument)
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ def term_components(bel_term)
98
+ return nil unless bel_term
99
+
100
+ {
101
+ :term => {
102
+ :fx => bel_term.fx,
103
+ :arguments => bel_term.arguments.map { |a| arg_components(a) }
104
+ }
105
+ }
106
+ end
107
+
108
+ def parameter_components(bel_parameter)
109
+ return nil unless bel_parameter
110
+
111
+ {
112
+ :parameter => {
113
+ :ns => bel_parameter.ns ? bel_parameter.ns.prefix : nil,
114
+ :value => bel_parameter.value.to_s
115
+ }
116
+ }
117
+ end
118
+ end
119
+
120
+ get '/api/expressions/*/completions/?' do
121
+ bel = params[:splat].first
122
+ caret_position = (params[:caret_position] || bel.length).to_i
123
+ halt 400 unless bel and caret_position
124
+
125
+ begin
126
+ completions = BEL::Completion.complete(bel, @search, caret_position)
127
+ rescue IndexError => ex
128
+ halt(
129
+ 400,
130
+ { 'Content-Type' => 'application/json' },
131
+ render_json({ :status => 400, :msg => ex.to_s })
132
+ )
133
+ end
134
+ halt 404 if completions.empty?
135
+
136
+ render_collection(
137
+ completions,
138
+ :completion,
139
+ :bel => bel,
140
+ :caret_position => caret_position
141
+ )
142
+ end
143
+
144
+ get '/api/expressions/*/components/?' do
145
+ bel = params[:splat].first
146
+ flatten = as_bool(params[:flatten])
147
+
148
+ statement = BEL::Script.parse(bel).find { |obj|
149
+ obj.is_a? BEL::Model::Statement
150
+ }
151
+ halt 404 unless statement
152
+
153
+ response.headers['Content-Type'] = 'application/json'
154
+ MultiJson.dump({
155
+ :expression_components => statement_components(statement, flatten),
156
+ :statement_short_form => statement.to_s
157
+ })
158
+ end
159
+
160
+ get '/api/expressions/*/components/terms?' do
161
+ bel = params[:splat].first
162
+ functions = CGI::parse(env["QUERY_STRING"])['function']
163
+ flatten = as_bool(params[:flatten])
164
+ inner_terms = as_bool(params[:inner_terms])
165
+
166
+ terms = BEL::Script.parse(bel).select { |obj|
167
+ obj.is_a? BEL::Model::Term
168
+ }
169
+
170
+ if !functions.empty?
171
+ functions = functions.map(&:to_sym)
172
+ terms = terms.select { |term|
173
+ functions.any? { |match|
174
+ term.fx.short_form == match || term.fx.long_form == match
175
+ }
176
+ }
177
+ end
178
+
179
+ if inner_terms
180
+ terms = terms.flat_map { |term|
181
+ term.arguments.select { |arg| arg.is_a? BEL::Model::Term }
182
+ }
183
+ end
184
+
185
+ terms = terms.to_a
186
+ halt 404 if terms.empty?
187
+
188
+ response.headers['Content-Type'] = 'application/json'
189
+ if flatten
190
+ MultiJson.dump({
191
+ :terms => terms.map { |term| term.to_bel }
192
+ })
193
+ else
194
+ MultiJson.dump({
195
+ :terms => terms.map { |term| term_components(term) }
196
+ })
197
+ end
198
+ end
199
+
200
+ # TODO Relies on LibBEL.bel_parse_statement which is not currently supported.
201
+ # get '/api/expressions/*/ortholog/:species' do
202
+ # bel = params[:splat].first
203
+ # species = params[:species]
204
+ # taxon_annotation = @annotations.find('taxon').first
205
+ #
206
+ # unless taxon_annotation
207
+ # halt(
208
+ # 404,
209
+ # { 'Content-Type' => 'application/json' },
210
+ # render_json({
211
+ # :status => 404,
212
+ # :msg => 'Could not find NCBI Taxonomy annotation.'
213
+ # })
214
+ # )
215
+ # end
216
+ #
217
+ # species = taxon_annotation.find(species).first
218
+ #
219
+ # if species
220
+ # species = species.identifier.to_s
221
+ # else
222
+ # halt(
223
+ # 400,
224
+ # { 'Content-Type' => 'application/json' },
225
+ # render_json({
226
+ # :status => 400,
227
+ # :msg => %Q{Could not find species "#{params[:species]}"}
228
+ # })
229
+ # )
230
+ # end
231
+ #
232
+ # bel_ast = BEL::Parser.parse(bel)
233
+ #
234
+ # if bel_ast.any?([@sequence_variation])
235
+ # msg = 'Could not orthologize sequence variation terms with location'
236
+ # halt(
237
+ # 404,
238
+ # { 'Content-Type' => 'application/json' },
239
+ # render_json({
240
+ # :status => 404,
241
+ # :msg => msg
242
+ # })
243
+ # )
244
+ # end
245
+ #
246
+ # param_transform = ParameterOrthologTransform.new(
247
+ # @namespaces, @annotations, species
248
+ # )
249
+ # transformed_ast = bel_ast.transform_tree([param_transform])
250
+ #
251
+ # if !param_transform.parameter_errors.empty?
252
+ # parameters = param_transform.parameter_errors.map { |p|
253
+ # p.join(':')
254
+ # }.join(', ')
255
+ # halt(
256
+ # 404,
257
+ # { 'Content-Type' => 'application/json' },
258
+ # render_json({
259
+ # :status => 404,
260
+ # :msg => "Could not orthologize #{parameters}"
261
+ # })
262
+ # )
263
+ # end
264
+ #
265
+ # # serialize AST to BEL
266
+ # bel_serialization = BELSerializationTransform.new
267
+ # transformed_ast.transform_tree([bel_serialization])
268
+ #
269
+ # # write response
270
+ # response.headers['Content-Type'] = 'application/json'
271
+ # MultiJson.dump({
272
+ # :original => bel,
273
+ # :species => params[:species],
274
+ # :orthologized => bel_serialization.bel_string
275
+ # })
276
+ # end
277
+
278
+ # BEL Syntax Validation
279
+ # TODO Move out to a separate route.
280
+ get '/api/expressions/*/syntax-validations/?' do
281
+ halt 501
282
+ end
283
+
284
+ # BEL Semantic Validations
285
+ # TODO Move out to a separate route.
286
+ get '/api/expressions/*/semantic-validations/?' do
287
+ halt 501
288
+ end
289
+
290
+ class SequenceVariationFunctionHasLocationPredicate
291
+ include BEL::LibBEL
292
+
293
+ SEQUENCE_VARIATION_FX = [
294
+ 'fus', 'fusion',
295
+ 'pmod', 'proteinModification',
296
+ 'sub', 'substitution',
297
+ 'trunc', 'truncation'
298
+ ]
299
+
300
+ def call(ast_node)
301
+ # check if AST node is a TERM
302
+ if !ast_node.is_a?(BelAstNodeToken) || ast_node.token_type != :BEL_TOKEN_TERM
303
+ return false
304
+ end
305
+
306
+ # check if AST node is a pmod TERM
307
+ if !SEQUENCE_VARIATION_FX.include?(ast_node.left.to_typed_node.value)
308
+ return false
309
+ end
310
+
311
+ # walk arg AST nodes until terminal
312
+ arg_node = ast_node.right.to_typed_node
313
+ while !(arg_node.left.pointer.null? && arg_node.right.pointer.null?)
314
+ # check if NV token child
315
+ arg_token = arg_node.left.to_typed_node
316
+
317
+ if arg_token.token_type == :BEL_TOKEN_NV
318
+ # true if namespace value is an integer
319
+ node_value = arg_token.right.to_typed_node.value
320
+ if integer?(node_value)
321
+ return true
322
+ end
323
+ end
324
+
325
+ # advance to the next ARG
326
+ arg_node = arg_node.right.to_typed_node
327
+ end
328
+
329
+ return false
330
+ end
331
+
332
+ private
333
+
334
+ def integer?(value)
335
+ begin
336
+ Integer(value)
337
+ return true
338
+ rescue ArgumentError
339
+ return false
340
+ end
341
+ end
342
+ end
343
+
344
+ class ParameterOrthologTransform
345
+ include BEL::LibBEL
346
+
347
+ NAMESPACE_PREFERENCE = [
348
+ "hgnc",
349
+ "mgi",
350
+ "rgd",
351
+ "gocc",
352
+ "scomp",
353
+ "meshcs",
354
+ "sfam",
355
+ "gobp",
356
+ "meshpp",
357
+ "chebi",
358
+ "schem",
359
+ "do",
360
+ "meshd",
361
+ "sdis",
362
+ "sp",
363
+ "affx",
364
+ "egid",
365
+ ]
366
+
367
+ def initialize(namespaces, annotations, species_tax_id)
368
+ @namespaces = namespaces
369
+ @orthology = OrthologAdapter.new(
370
+ namespaces, species_tax_id
371
+ )
372
+ @species_tax_id = species_tax_id
373
+ @parameter_errors = []
374
+ end
375
+
376
+ def parameter_errors
377
+ @parameter_errors.uniq
378
+ end
379
+
380
+ def call(ast_node)
381
+ if ast_node.is_a?(BelAstNodeToken) &&
382
+ ast_node.token_type == :BEL_TOKEN_NV
383
+
384
+ ns_value = [
385
+ ast_node.left.to_typed_node.value,
386
+ ast_node.right.to_typed_node.value
387
+ ]
388
+ orthologs = @orthology[ns_value]
389
+ if !orthologs.empty?
390
+ orthologs.sort_by! { |ortholog| namespace_preference(ortholog) }
391
+ ortholog = orthologs.first
392
+ BEL::LibBEL::bel_free_ast_node(ast_node.left.pointer)
393
+ ast_node.left = BelAstNode.new(
394
+ bel_new_ast_node_value(:BEL_VALUE_PFX, ortholog[0].upcase)
395
+ )
396
+
397
+ BEL::LibBEL::bel_free_ast_node(ast_node.right.pointer)
398
+ ast_node.right = BelAstNode.new(
399
+ bel_new_ast_node_value(:BEL_VALUE_VAL, ortholog[1])
400
+ )
401
+ else
402
+ # flag as ortholog error if this parameter has a namespace and
403
+ # the namespace value is either not known or its species differs
404
+ # from our target
405
+ if ns_value[0] != nil
406
+ namespace, value = ns_value
407
+ namespace = @namespaces.find(namespace).first
408
+ if namespace
409
+ value = namespace.find(value).first
410
+ if !value || value.fromSpecies != @species_tax_id
411
+ @parameter_errors << value
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
418
+
419
+ private
420
+
421
+ def namespace_preference(ortholog)
422
+ NAMESPACE_PREFERENCE.index(ortholog[0])
423
+ end
424
+ end
425
+
426
+ class BELSerializationTransform
427
+ include BEL::LibBEL
428
+
429
+ attr_reader :bel_string
430
+
431
+ def initialize
432
+ @bel_string = ""
433
+ end
434
+
435
+ def call(ast_node)
436
+ if ast_node.is_a?(BelAstNodeToken)
437
+ case ast_node.token_type
438
+ when :BEL_TOKEN_STATEMENT
439
+ when :BEL_TOKEN_SUBJECT
440
+ when :BEL_TOKEN_OBJECT
441
+ when :BEL_TOKEN_REL
442
+ when :BEL_TOKEN_TERM
443
+ when :BEL_TOKEN_ARG
444
+ when :BEL_TOKEN_NV
445
+ end
446
+ else
447
+ case ast_node.value_type
448
+ when :BEL_VALUE_FX
449
+ function(ast_node.value)
450
+ when :BEL_VALUE_REL
451
+ relationship(ast_node.value)
452
+ when :BEL_VALUE_PFX
453
+ namespace_prefix(ast_node.value)
454
+ when :BEL_VALUE_VAL
455
+ namespace_value(ast_node.value)
456
+ end
457
+ end
458
+ end
459
+
460
+ def between(ast_node)
461
+ if ast_node.is_a?(BelAstNodeToken)
462
+ case ast_node.token_type
463
+ when :BEL_TOKEN_STATEMENT
464
+ when :BEL_TOKEN_SUBJECT
465
+ when :BEL_TOKEN_OBJECT
466
+ when :BEL_TOKEN_REL
467
+ when :BEL_TOKEN_TERM
468
+ when :BEL_TOKEN_ARG
469
+ token_arg(ast_node)
470
+ when :BEL_TOKEN_NV
471
+ end
472
+ end
473
+ end
474
+
475
+ def after(ast_node)
476
+ if ast_node.is_a?(BelAstNodeToken)
477
+ case ast_node.token_type
478
+ when :BEL_TOKEN_STATEMENT
479
+ when :BEL_TOKEN_SUBJECT
480
+ when :BEL_TOKEN_OBJECT
481
+ when :BEL_TOKEN_REL
482
+ when :BEL_TOKEN_TERM
483
+ @bel_string.concat(')')
484
+ when :BEL_TOKEN_ARG
485
+ when :BEL_TOKEN_NV
486
+ end
487
+ end
488
+ end
489
+
490
+ private
491
+
492
+ def token_arg(ast_node)
493
+ chained_arg_node = ast_node.right
494
+ if !chained_arg_node.pointer.null?
495
+ chained_arg_node = chained_arg_node.to_typed_node
496
+ if !chained_arg_node.left.pointer.null? ||
497
+ !chained_arg_node.right.pointer.null?
498
+ @bel_string.concat(', ')
499
+ end
500
+ end
501
+ end
502
+
503
+ def function(fx)
504
+ @bel_string.concat(fx).concat('(')
505
+ end
506
+
507
+ def relationship(rel)
508
+ @bel_string.concat(" #{rel} ")
509
+ end
510
+
511
+ def namespace_prefix(prefix)
512
+ return unless prefix
513
+ @bel_string.concat(prefix).concat(':')
514
+ end
515
+
516
+ def namespace_value(value)
517
+ @bel_string.concat(value)
518
+ end
519
+ end
520
+
521
+ # Hash-like
522
+ class OrthologAdapter
523
+
524
+ EMPTY = [].freeze
525
+
526
+ def initialize(namespaces, species_tax_id)
527
+ @namespaces = namespaces
528
+ @species_tax_id = species_tax_id
529
+ end
530
+
531
+ def [](key)
532
+ namespace, value = key
533
+
534
+ if value.start_with?('"') && value.end_with?('"')
535
+ value = value[1...-1]
536
+ end
537
+
538
+ namespace = @namespaces.find(namespace).first
539
+ return EMPTY unless namespace
540
+ value = namespace.find(value).first
541
+ return EMPTY unless value
542
+ orthologs = value.orthologs.select { |orth|
543
+ orth.fromSpecies == @species_tax_id
544
+ }.to_a
545
+ return EMPTY if orthologs.empty?
546
+
547
+ orthologs.map! { |ortholog_value|
548
+ [
549
+ ortholog_value.namespace.prefix,
550
+ ortholog_value.prefLabel
551
+ ]
552
+ }
553
+ orthologs
554
+ end
555
+ end
556
+ end
557
+ end
558
+ end
559
+ # vim: ts=2 sw=2:
560
+ # encoding: utf-8