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.
- checksums.yaml +7 -0
- data/.gemspec +65 -0
- data/CHANGELOG.md +22 -0
- data/INSTALL.md +19 -0
- data/INSTALL_RUBY.md +107 -0
- data/LICENSE +191 -0
- data/README.md +208 -0
- data/app/openbel/api/app.rb +83 -0
- data/app/openbel/api/config.rb +45 -0
- data/app/openbel/api/config.ru +3 -0
- data/app/openbel/api/helpers/pager.rb +109 -0
- data/app/openbel/api/middleware/auth.rb +112 -0
- data/app/openbel/api/resources/adapters/basic_json.rb +52 -0
- data/app/openbel/api/resources/annotation.rb +141 -0
- data/app/openbel/api/resources/base.rb +16 -0
- data/app/openbel/api/resources/completion.rb +89 -0
- data/app/openbel/api/resources/evidence.rb +115 -0
- data/app/openbel/api/resources/evidence_transform.rb +143 -0
- data/app/openbel/api/resources/function.rb +98 -0
- data/app/openbel/api/resources/match_result.rb +79 -0
- data/app/openbel/api/resources/namespace.rb +174 -0
- data/app/openbel/api/routes/annotations.rb +168 -0
- data/app/openbel/api/routes/authenticate.rb +108 -0
- data/app/openbel/api/routes/base.rb +326 -0
- data/app/openbel/api/routes/datasets.rb +519 -0
- data/app/openbel/api/routes/evidence.rb +330 -0
- data/app/openbel/api/routes/expressions.rb +560 -0
- data/app/openbel/api/routes/functions.rb +41 -0
- data/app/openbel/api/routes/namespaces.rb +382 -0
- data/app/openbel/api/routes/root.rb +39 -0
- data/app/openbel/api/schemas.rb +34 -0
- data/app/openbel/api/schemas/annotation_collection.schema.json +20 -0
- data/app/openbel/api/schemas/annotation_resource.schema.json +36 -0
- data/app/openbel/api/schemas/annotation_value_collection.schema.json +21 -0
- data/app/openbel/api/schemas/annotation_value_resource.schema.json +35 -0
- data/app/openbel/api/schemas/completion_collection.schema.json +21 -0
- data/app/openbel/api/schemas/completion_resource.schema.json +146 -0
- data/app/openbel/api/schemas/evidence.schema.json +198 -0
- data/app/openbel/api/schemas/evidence_collection.schema.json +98 -0
- data/app/openbel/api/schemas/evidence_resource.schema.json +29 -0
- data/app/openbel/api/schemas/namespace_value_collection.schema.json +21 -0
- data/app/openbel/api/schemas/namespace_value_resource.schema.json +43 -0
- data/app/openbel/api/util.rb +11 -0
- data/bin/openbel-api +78 -0
- data/bin/openbel-config +46 -0
- data/config/async_evidence.rb +12 -0
- data/config/async_jena.rb +14 -0
- data/config/config.yml +31 -0
- data/config/server_config.rb +184 -0
- data/lib/openbel/api/cache/cache.rb +30 -0
- data/lib/openbel/api/config/config.rb +33 -0
- data/lib/openbel/api/evidence/api.rb +39 -0
- data/lib/openbel/api/evidence/facet_api.rb +18 -0
- data/lib/openbel/api/evidence/facet_filter.rb +83 -0
- data/lib/openbel/api/evidence/mongo.rb +247 -0
- data/lib/openbel/api/evidence/mongo_facet.rb +105 -0
- data/lib/openbel/api/helpers/dependency_graph.rb +52 -0
- data/lib/openbel/api/model/rdf_resource.rb +74 -0
- data/lib/openbel/api/plugin/cache/kyotocabinet.rb +85 -0
- data/lib/openbel/api/plugin/configure_plugins.rb +97 -0
- data/lib/openbel/api/plugin/evidence/evidence.rb +58 -0
- data/lib/openbel/api/plugin/plugin.rb +99 -0
- data/lib/openbel/api/plugin/plugin_manager.rb +20 -0
- data/lib/openbel/api/plugin/plugin_repository.rb +60 -0
- data/lib/openbel/api/storage/cache_proxy.rb +74 -0
- data/lib/openbel/api/storage/triple_storage.rb +43 -0
- 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
|