openbel-api 0.4.0-java

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 (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