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,326 @@
1
+ require 'bel'
2
+ require 'json_schema'
3
+ require 'multi_json'
4
+ require_relative '../resources/annotation'
5
+ require_relative '../resources/completion'
6
+ require_relative '../resources/evidence'
7
+ require_relative '../resources/function'
8
+ require_relative '../resources/match_result'
9
+ require_relative '../resources/namespace'
10
+ require_relative '../schemas'
11
+
12
+ module OpenBEL
13
+ module Routes
14
+
15
+ class Base < Sinatra::Application
16
+ include OpenBEL::Resource::Annotations
17
+ include OpenBEL::Resource::Evidence
18
+ include OpenBEL::Resource::Expressions
19
+ include OpenBEL::Resource::Functions
20
+ include OpenBEL::Resource::MatchResults
21
+ include OpenBEL::Resource::Namespaces
22
+ include OpenBEL::Schemas
23
+
24
+ DEFAULT_CONTENT_TYPE = 'application/hal+json'
25
+ SPOKEN_CONTENT_TYPES = %w[application/hal+json application/json]
26
+ SPOKEN_CONTENT_TYPES.concat(
27
+ BEL::Translator.plugins.values.flat_map { |p| p.media_types.map(&:to_s) }
28
+ )
29
+
30
+ SCHEMA_BASE_URL = 'http://next.belframework.org/schemas/'
31
+ RESOURCE_SERIALIZERS = {
32
+ :annotation => AnnotationResourceSerializer,
33
+ :annotation_collection => AnnotationCollectionSerializer,
34
+ :annotation_value => AnnotationValueResourceSerializer,
35
+ :completion => CompletionResourceSerializer,
36
+ :completion_collection => CompletionCollectionSerializer,
37
+ :function => FunctionResourceSerializer,
38
+ :function_collection => FunctionCollectionSerializer,
39
+ :match_result => MatchResultResourceSerializer,
40
+ :match_result_collection => MatchResultCollectionSerializer,
41
+ :namespace => NamespaceResourceSerializer,
42
+ :namespace_collection => NamespaceCollectionSerializer,
43
+ :namespace_value => NamespaceValueResourceSerializer,
44
+ :namespace_value_collection => NamespaceValueCollectionSerializer,
45
+ :evidence => EvidenceSerializer,
46
+ :evidence_resource => EvidenceResourceSerializer,
47
+ :evidence_collection => EvidenceCollectionSerializer
48
+ }
49
+
50
+ disable :protection
51
+
52
+ before do
53
+ unless request.preferred_type(SPOKEN_CONTENT_TYPES)
54
+ halt 406
55
+ end
56
+ end
57
+
58
+ helpers do
59
+
60
+ def as_bool(value)
61
+ value == nil ? false : value.to_s =~ (/^(true|t|yes|y|1|on)$/i)
62
+ end
63
+
64
+ def wildcard_match(match)
65
+ match.to_s.split(/\W/).map { |v| "*#{v}*"}.join(' ')
66
+ end
67
+
68
+ def request_headers
69
+ env.inject({}) { |hdrs, (k,v)|
70
+ hdrs[$1.downcase] = v if k =~ /^http_(.*)/i
71
+ hdrs
72
+ }
73
+ end
74
+
75
+ def base_url
76
+ env['HTTP_X_REAL_BASE_URL'] ||
77
+ "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}"
78
+ end
79
+
80
+ def url
81
+ env['HTTP_X_REAL_URL'] ||
82
+ "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}/#{env['PATH_INFO']}"
83
+ end
84
+
85
+ def schema_url(name)
86
+ SCHEMA_BASE_URL + "#{name}.schema.json"
87
+ end
88
+
89
+ def validate_media_type!(content_type, options = {})
90
+ ctype = request.content_type
91
+ valid = ctype.start_with? content_type
92
+ if options[:profile]
93
+ valid &= (%r{profile=#{options[:profile]}} =~ ctype)
94
+ end
95
+
96
+ unless valid
97
+ halt(
98
+ 415,
99
+ {
100
+ 'Content-Type' => 'application/json'
101
+ },
102
+ render_json({
103
+ :status => 415,
104
+ :msg => "Invalid media type #{ctype} (profile: #{options[:profile]})"
105
+ })
106
+ )
107
+ end
108
+ end
109
+
110
+ def read_evidence
111
+ halt 415 unless ::BEL.translator(request.media_type)
112
+ ::BEL.evidence(request.body, request.media_type, :symbolize_keys => true)
113
+ end
114
+
115
+ def read_json
116
+ request.body.rewind
117
+ begin
118
+ MultiJson.load(request.body.read, :symbolize_keys => true)
119
+ rescue MultiJson::ParseError => ex
120
+ halt(
121
+ 400,
122
+ {
123
+ 'Content-Type' => 'application/json'
124
+ },
125
+ render_json({
126
+ :status => 400,
127
+ :msg => 'Invalid JSON body.',
128
+ :detail => ex.cause.to_s
129
+ })
130
+ )
131
+ end
132
+ end
133
+
134
+ def read_filter(filter_json)
135
+ begin
136
+ MultiJson.load filter_json
137
+ rescue MultiJson::ParseError => ex
138
+ halt(
139
+ 400,
140
+ {
141
+ 'Content-Type' => 'application/json'
142
+ },
143
+ render_json({
144
+ :status => 400,
145
+ :msg => "Invalid JSON filter: #{filter_json}.",
146
+ :detail => ex.cause.to_s
147
+ })
148
+ )
149
+ end
150
+ end
151
+
152
+ def render_json(obj, media_type = 'application/hal+json', profile = nil)
153
+ ctype =
154
+ if profile
155
+ "#{media_type}; profile=#{profile}"
156
+ else
157
+ media_type
158
+ end
159
+ response.headers['Content-Type'] = ctype.to_s
160
+ MultiJson.dump obj
161
+ end
162
+
163
+ def validate_schema(data, type)
164
+ self.validate(data, type)
165
+ end
166
+
167
+ def render_resource(obj, type, options = {})
168
+ media_type = 'application/hal+json'
169
+ resource_context = {
170
+ :base_url => base_url,
171
+ :url => url
172
+ }.merge(options)
173
+
174
+ type_class = type.to_s.split('_').map(&:capitalize).join
175
+ type_serializer = self.class.const_get("#{type_class}Serializer")
176
+ resource_serializer = self.class.const_get("#{type_class}ResourceSerializer")
177
+
178
+ adapter = Oat::Adapters::HAL
179
+ if options[:adapter]
180
+ adapter = options[:adapter]
181
+ end
182
+
183
+ resource = resource_serializer.new(
184
+ type_serializer.new(obj, resource_context, adapter).to_hash,
185
+ resource_context,
186
+ adapter
187
+ ).to_hash
188
+
189
+ render_json(resource.to_hash, media_type)
190
+ end
191
+
192
+ def render_collection(collection, type, options = {})
193
+ media_type = 'application/hal+json'
194
+ resource_context = {
195
+ :base_url => base_url,
196
+ :url => url
197
+ }.merge(options)
198
+
199
+ type_class = type.to_s.split('_').map(&:capitalize).join
200
+ serializer = self.class.const_get("#{type_class}Serializer")
201
+ type_serializer = self.class.const_get("#{type_class}ResourceSerializer")
202
+ resource_serializer = self.class.const_get("#{type_class}CollectionSerializer")
203
+
204
+ adapter = Oat::Adapters::HAL
205
+ if options[:adapter]
206
+ adapter = options[:adapter]
207
+ end
208
+
209
+ resource = resource_serializer.new(
210
+ collection.map { |obj|
211
+ single = serializer.new(obj, resource_context, adapter).to_hash
212
+ type_serializer.new(single, resource_context, adapter).to_hash
213
+ },
214
+ resource_context,
215
+ adapter
216
+ ).to_hash
217
+
218
+ render_json(resource.to_hash, media_type)
219
+ end
220
+
221
+ def render(obj, type, options = {})
222
+ media_type = 'application/hal+json'
223
+ resource_context = {
224
+ :base_url => base_url,
225
+ :url => url
226
+ }.merge(options)
227
+
228
+ serializer_class = RESOURCE_SERIALIZERS[type]
229
+ unless serializer_class
230
+ raise NotImplementedError.new("Cannot serialize the #{type} resource.")
231
+ end
232
+
233
+ adapter = Oat::Adapters::HAL
234
+
235
+ # XXX hack to fix OpenBEL/openbel-server#33
236
+ if options[:as_array]
237
+ render_json(
238
+ {
239
+ type => [serializer_class.new(obj, resource_context, adapter).to_hash]
240
+ },
241
+ media_type
242
+ )
243
+ else
244
+ render_json(
245
+ serializer_class.new(obj, resource_context, adapter).to_hash,
246
+ media_type
247
+ )
248
+ end
249
+ end
250
+
251
+ def stream_resource_collection(type, collection, facets, options = {})
252
+ serializer_class = RESOURCE_SERIALIZERS[type]
253
+ unless serializer_class
254
+ raise NotImplementedError.new("Cannot serialize the #{type} resource collection.")
255
+ end
256
+
257
+ media_type = resolve_supported_content_type(request)
258
+ resource_context = {
259
+ :base_url => base_url,
260
+ :url => url
261
+ }.merge(options)
262
+
263
+ adapter = Oat::Adapters::HAL
264
+
265
+ response.headers['Content-Type'] = media_type
266
+ collection_enum = collection.to_enum
267
+
268
+ stream do |response|
269
+ response << %Q({"#{type}": [)
270
+ item = collection_enum.next
271
+ response << render_json(
272
+ serializer_class.new(item, resource_context, adapter).to_hash,
273
+ media_type
274
+ )
275
+ while true
276
+ begin
277
+ item = collection_enum.next
278
+ response << (',' + render_json(
279
+ serializer_class.new(item, resource_context, adapter).to_hash,
280
+ media_type
281
+ ))
282
+ rescue StopIteration
283
+ break
284
+ end
285
+ end
286
+ response << ']'
287
+
288
+ if facets
289
+ response << ',"facets": ['
290
+
291
+ facet_enum = facets.to_enum
292
+ facet_item = facet_enum.next
293
+ filter = MultiJson.load(facet_item['_id'])
294
+ response << MultiJson.dump({
295
+ :category => filter['category'].to_sym,
296
+ :name => filter['name'].to_sym,
297
+ :value => filter['value'],
298
+ :filter => facet_item['_id'],
299
+ :count => facet_item['count']
300
+ })
301
+ while true
302
+ begin
303
+ facet_item = facet_enum.next
304
+ filter = MultiJson.load(facet_item['_id'])
305
+ response << (',' + MultiJson.dump({
306
+ :category => filter['category'].to_sym,
307
+ :name => filter['name'].to_sym,
308
+ :value => filter['value'],
309
+ :filter => facet_item['_id'],
310
+ :count => facet_item['count']
311
+ }))
312
+ rescue StopIteration
313
+ break
314
+ end
315
+ end
316
+ response << ']'
317
+ end
318
+
319
+ response << '}'
320
+ end
321
+ end
322
+
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,519 @@
1
+ require 'bel'
2
+ require 'rdf'
3
+ require 'cgi'
4
+ require 'multi_json'
5
+ require 'openbel/api/evidence/mongo'
6
+ require 'openbel/api/evidence/facet_filter'
7
+ require_relative '../resources/evidence_transform'
8
+ require_relative '../helpers/pager'
9
+
10
+ module OpenBEL
11
+ module Routes
12
+
13
+ class Datasets < Base
14
+ include OpenBEL::Evidence::FacetFilter
15
+ include OpenBEL::Resource::Evidence
16
+ include OpenBEL::Helpers
17
+
18
+ DEFAULT_TYPE = 'application/hal+json'
19
+
20
+ ACCEPTED_TYPES = {
21
+ :bel => 'application/bel',
22
+ :xml => 'application/xml',
23
+ :xbel => 'application/xml',
24
+ :json => 'application/json',
25
+ }
26
+
27
+ def initialize(app)
28
+ super
29
+
30
+ # Evidence API using Mongo.
31
+ @api = OpenBEL::Evidence::Evidence.new(
32
+ :host => OpenBEL::Settings[:evidence_store][:mongo][:host],
33
+ :port => OpenBEL::Settings[:evidence_store][:mongo][:port],
34
+ :database => OpenBEL::Settings[:evidence_store][:mongo][:database]
35
+ )
36
+
37
+ # RdfRepository using Jena.
38
+ @rr = BEL::RdfRepository.plugins[:jena].create_repository(
39
+ :tdb_directory => OpenBEL::Settings[:resource_rdf][:jena][:tdb_directory]
40
+ )
41
+
42
+ # Load RDF monkeypatches.
43
+ BEL::Translator.plugins[:rdf].create_translator
44
+
45
+ # Annotations using RdfRepository
46
+ annotations = BEL::Resource::Annotations.new(@rr)
47
+ @annotation_transform = AnnotationTransform.new(annotations)
48
+ end
49
+
50
+ # Hang on to the Rack IO in order to do unbuffered reads.
51
+ # use Rack::Config do |env|
52
+ # env['rack.input'], env['data.input'] = StringIO.new, env['rack.input']
53
+ # end
54
+
55
+ configure do
56
+ ACCEPTED_TYPES.each do |ext, mime_type|
57
+ mime_type(ext, mime_type)
58
+ end
59
+ end
60
+
61
+ helpers do
62
+
63
+ def check_dataset(io, type)
64
+ begin
65
+ evidence = BEL.evidence(io, type).each.first
66
+
67
+ unless evidence
68
+ halt(
69
+ 400,
70
+ { 'Content-Type' => 'application/json' },
71
+ render_json({ :status => 400, :msg => 'No BEL evidence was provided. Evidence is required to infer dataset information.' })
72
+ )
73
+ end
74
+
75
+ void_dataset_uri = RDF::URI("#{base_url}/api/datasets/#{self.generate_uuid}")
76
+
77
+ void_dataset = evidence.to_void_dataset(void_dataset_uri)
78
+ unless void_dataset
79
+ halt(
80
+ 400,
81
+ { 'Content-Type' => 'application/json' },
82
+ render_json({ :status => 400, :msg => 'The dataset document does not contain a document header.' })
83
+ )
84
+ end
85
+
86
+ identifier_statement = void_dataset.query(
87
+ RDF::Statement.new(void_dataset_uri, RDF::DC.identifier, nil)
88
+ ).to_a.first
89
+ unless identifier_statement
90
+ halt(
91
+ 400,
92
+ { 'Content-Type' => 'application/json' },
93
+ render_json(
94
+ {
95
+ :status => 400,
96
+ :msg => 'The dataset document does not contain the Name or Version needed to build an identifier.'
97
+ }
98
+ )
99
+ )
100
+ end
101
+
102
+ datasets = @rr.query_pattern(RDF::Statement.new(nil, RDF.type, RDF::VOID.Dataset))
103
+ existing_dataset = datasets.find { |dataset_statement|
104
+ @rr.has_statement?(
105
+ RDF::Statement.new(dataset_statement.subject, RDF::DC.identifier, identifier_statement.object)
106
+ )
107
+ }
108
+
109
+ if existing_dataset
110
+ dataset_uri = existing_dataset.subject.to_s
111
+ headers 'Location' => dataset_uri
112
+ halt(
113
+ 409,
114
+ { 'Content-Type' => 'application/json' },
115
+ render_json(
116
+ {
117
+ :status => 409,
118
+ :msg => %Q{The dataset document matches an existing dataset resource by identifier "#{identifier_statement.object}".},
119
+ :location => dataset_uri
120
+ }
121
+ )
122
+ )
123
+ end
124
+
125
+ [void_dataset_uri, void_dataset]
126
+ ensure
127
+ io.rewind
128
+ end
129
+ end
130
+
131
+ def dataset_exists?(uri)
132
+ @rr.has_statement?(
133
+ RDF::Statement.new(uri, RDF.type, RDF::VOID.Dataset)
134
+ )
135
+ end
136
+
137
+ def retrieve_dataset(uri)
138
+ dataset = {}
139
+ identifier = @rr.query(
140
+ RDF::Statement.new(uri, RDF::DC.identifier, nil)
141
+ ).first
142
+ dataset[:identifier] = identifier.object.to_s if identifier
143
+
144
+ title = @rr.query(
145
+ RDF::Statement.new(uri, RDF::DC.title, nil)
146
+ ).first
147
+ dataset[:title] = title.object.to_s if title
148
+
149
+ description = @rr.query(
150
+ RDF::Statement.new(uri, RDF::DC.description, nil)
151
+ ).first
152
+ dataset[:description] = description.object.to_s if description
153
+
154
+ waiver = @rr.query(
155
+ RDF::Statement.new(uri, RDF::URI('http://vocab.org/waiver/terms/waiver'), nil)
156
+ ).first
157
+ dataset[:waiver] = waiver.object.to_s if waiver
158
+
159
+ creator = @rr.query(
160
+ RDF::Statement.new(uri, RDF::DC.creator, nil)
161
+ ).first
162
+ dataset[:creator] = creator.object.to_s if creator
163
+
164
+ license = @rr.query(
165
+ RDF::Statement.new(uri, RDF::DC.license, nil)
166
+ ).first
167
+ dataset[:license] = license.object.to_s if license
168
+
169
+ publisher = @rr.query(
170
+ RDF::Statement.new(uri, RDF::DC.publisher, nil)
171
+ ).first
172
+ if publisher
173
+ publisher.object
174
+ contact_info = @rr.query(
175
+ RDF::Statement.new(publisher.object, RDF::FOAF.mbox, nil)
176
+ ).first
177
+ dataset[:contact_info] = contact_info.object.to_s if contact_info
178
+ end
179
+
180
+ dataset
181
+ end
182
+ end
183
+
184
+ options '/api/datasets' do
185
+ response.headers['Allow'] = 'OPTIONS,POST,GET'
186
+ status 200
187
+ end
188
+
189
+ options '/api/datasets/:id' do
190
+ response.headers['Allow'] = 'OPTIONS,GET,PUT,DELETE'
191
+ status 200
192
+ end
193
+
194
+ post '/api/datasets' do
195
+ if request.media_type == 'multipart/form-data' && params['file']
196
+ io, filename, type = params['file'].values_at(:tempfile, :filename, :type)
197
+ unless ACCEPTED_TYPES.values.include?(type)
198
+ type = mime_type(File.extname(filename))
199
+ end
200
+
201
+ halt(
202
+ 415,
203
+ { 'Content-Type' => 'application/json' },
204
+ render_json({
205
+ :status => 415,
206
+ :msg => %Q{
207
+ [Form data] Do not support content type for "#{type || filename}" when processing datasets from the "file" form parameter.
208
+ The following content types are allowed: #{ACCEPTED_TYPES.values.join(', ')}. The "file" form parameter type can also be inferred by the following file extensions: #{ACCEPTED_TYPES.keys.join(', ')}} })
209
+ ) unless ACCEPTED_TYPES.values.include?(type)
210
+ elsif ACCEPTED_TYPES.values.include?(request.media_type)
211
+ type = request.media_type
212
+ io = request.body
213
+
214
+ halt(
215
+ 415,
216
+ { 'Content-Type' => 'application/json' },
217
+ render_json({
218
+ :status => 415,
219
+ :msg => %Q{[POST data] Do not support content type #{type} when processing datasets. The following content types
220
+ are allowed in the "Content-Type" header: #{ACCEPTED_TYPES.values.join(', ')}} })
221
+ ) unless ACCEPTED_TYPES.values.include?(type)
222
+ else
223
+ halt(
224
+ 400,
225
+ { 'Content-Type' => 'application/json' },
226
+ render_json({
227
+ :status => 400,
228
+ :msg => %Q{Please POST data using a supported "Content-Type" or a "file" parameter using
229
+ the "multipart/form-data" content type. Allowed dataset content types are: #{ACCEPTED_TYPES.values.join(', ')}} })
230
+ )
231
+ end
232
+
233
+ # Check dataset in request for suitability and conflict with existing resources.
234
+ void_dataset_uri, void_dataset = check_dataset(io, type)
235
+
236
+ # Create dataset in RDF.
237
+ @rr.insert_statements(void_dataset)
238
+
239
+ dataset = retrieve_dataset(void_dataset_uri)
240
+
241
+ # Add slices of read evidence objects; save to Mongo and RDF.
242
+ BEL.evidence(io, type).each.lazy.each_slice(500) do |slice|
243
+ slice.map! do |ev|
244
+ # Standardize annotations from experiment_context.
245
+ @annotation_transform.transform_evidence!(ev, base_url)
246
+
247
+ # Add filterable metadata field for dataset identifier.
248
+ ev.metadata[:dataset] = dataset[:identifier]
249
+
250
+ facets = map_evidence_facets(ev)
251
+ ev.bel_statement = ev.bel_statement.to_s
252
+ hash = ev.to_h
253
+ hash[:facets] = facets
254
+
255
+ # Create dataset field for efficient removal.
256
+ hash[:_dataset] = dataset[:identifier]
257
+ hash
258
+ end
259
+
260
+ _ids = @api.create_evidence(slice)
261
+
262
+ dataset_parts = _ids.map { |object_id|
263
+ RDF::Statement.new(void_dataset_uri, RDF::DC.hasPart, object_id.to_s)
264
+ }
265
+ @rr.insert_statements(dataset_parts)
266
+ end
267
+
268
+ status 201
269
+ headers 'Location' => void_dataset_uri.to_s
270
+ end
271
+
272
+ get '/api/datasets/:id' do
273
+ id = params[:id]
274
+ void_dataset_uri = RDF::URI("#{base_url}/api/datasets/#{id}")
275
+ halt 404 unless dataset_exists?(void_dataset_uri)
276
+
277
+ status 200
278
+ render_json({
279
+ :dataset => retrieve_dataset(void_dataset_uri),
280
+ :_links => {
281
+ :self => {
282
+ :type => 'dataset',
283
+ :href => void_dataset_uri.to_s
284
+ },
285
+ :evidence_collection => {
286
+ :type => 'evidence_collection',
287
+ :href => "#{base_url}/api/datasets/#{id}/evidence"
288
+ }
289
+ }
290
+ })
291
+ end
292
+
293
+ get '/api/datasets/:id/evidence' do
294
+ id = params[:id]
295
+ void_dataset_uri = RDF::URI("#{base_url}/api/datasets/#{id}")
296
+ halt 404 unless dataset_exists?(void_dataset_uri)
297
+
298
+ dataset = retrieve_dataset(void_dataset_uri)
299
+
300
+ start = (params[:start] || 0).to_i
301
+ size = (params[:size] || 0).to_i
302
+ faceted = as_bool(params[:faceted])
303
+ max_values_per_facet = (params[:max_values_per_facet] || 0).to_i
304
+
305
+ # check filters
306
+ filters = []
307
+ filter_params = CGI::parse(env["QUERY_STRING"])['filter']
308
+ filter_params.each do |filter|
309
+ filter = read_filter(filter)
310
+ halt 400 unless ['category', 'name', 'value'].all? { |f| filter.include? f}
311
+
312
+ if filter['category'] == 'fts' && filter['name'] == 'search'
313
+ unless filter['value'].to_s.length > 1
314
+ halt(
315
+ 400,
316
+ { 'Content-Type' => 'application/json' },
317
+ render_json({
318
+ :status => 400,
319
+ :msg => 'Full-text search filter values must be larger than one.'
320
+ })
321
+ )
322
+ end
323
+ end
324
+
325
+ # Remove dataset filters since we're filtering a specific one already.
326
+ next if filter.values_at('category', 'name') == ['metadata', 'dataset']
327
+
328
+ filters << filter
329
+ end
330
+
331
+ collection_total = @api.count_evidence
332
+ filtered_total = @api.count_evidence(filters)
333
+ page_results = @api.find_dataset_evidence(dataset, filters, start, size, faceted)
334
+
335
+ accept_type = request.accept.find { |accept_entry|
336
+ ACCEPTED_TYPES.values.include?(accept_entry.to_s)
337
+ }
338
+ accept_type ||= DEFAULT_TYPE
339
+
340
+ if params[:format]
341
+ translator = BEL::Translator.plugins[params[:format].to_sym]
342
+ halt 501 if !translator || translator.id == :rdf
343
+ accept_type = [translator.media_types].flatten.first
344
+ end
345
+
346
+ if accept_type == DEFAULT_TYPE
347
+ evidence = page_results[:cursor].map { |item|
348
+ item.delete('facets')
349
+ item
350
+ }.to_a
351
+
352
+ facets = page_results[:facets]
353
+
354
+ halt 404 if evidence.empty?
355
+
356
+ pager = Pager.new(start, size, filtered_total)
357
+
358
+ options = {
359
+ :start => start,
360
+ :size => size,
361
+ :filters => filter_params,
362
+ :metadata => {
363
+ :collection_paging => {
364
+ :total => collection_total,
365
+ :total_filtered => pager.total_size,
366
+ :total_pages => pager.total_pages,
367
+ :current_page => pager.current_page,
368
+ :current_page_size => evidence.size,
369
+ }
370
+ }
371
+ }
372
+
373
+ if facets
374
+ # group by category/name
375
+ hashed_values = Hash.new { |hash, key| hash[key] = [] }
376
+ facets.each { |facet|
377
+ filter = read_filter(facet['_id'])
378
+ category, name = filter.values_at('category', 'name')
379
+ next if !category || !name
380
+
381
+ key = [category.to_sym, name.to_sym]
382
+ facet_obj = {
383
+ :value => filter['value'],
384
+ :filter => facet['_id'],
385
+ :count => facet['count']
386
+ }
387
+ hashed_values[key] << facet_obj
388
+ }
389
+
390
+ if max_values_per_facet == 0
391
+ facet_hashes = hashed_values.map { |(category, name), value_objects|
392
+ {
393
+ :category => category,
394
+ :name => name,
395
+ :values => value_objects
396
+ }
397
+ }
398
+ else
399
+ facet_hashes = hashed_values.map { |(category, name), value_objects|
400
+ {
401
+ :category => category,
402
+ :name => name,
403
+ :values => value_objects.take(max_values_per_facet)
404
+ }
405
+ }
406
+ end
407
+
408
+ options[:facets] = facet_hashes
409
+ end
410
+
411
+ # pager links
412
+ options[:previous_page] = pager.previous_page
413
+ options[:next_page] = pager.next_page
414
+
415
+ render_collection(evidence, :evidence, options)
416
+ else
417
+ out_translator = BEL.translator(accept_type)
418
+ extension = ACCEPTED_TYPES.key(accept_type.to_s)
419
+
420
+ response.headers['Content-Type'] = accept_type
421
+ status 200
422
+ attachment "#{dataset[:identifier].gsub(/[^\w]/, '_')}.#{extension}"
423
+ stream :keep_open do |response|
424
+ cursor = page_results[:cursor]
425
+ json_evidence_enum = cursor.lazy.map { |evidence|
426
+ evidence.delete('facets')
427
+ evidence.delete('_id')
428
+ evidence.keys.each do |key|
429
+ evidence[key.to_sym] = evidence.delete(key)
430
+ end
431
+ BEL::Model::Evidence.create(evidence)
432
+ }
433
+
434
+ out_translator.write(json_evidence_enum) do |converted_evidence|
435
+ response << converted_evidence
436
+ end
437
+ end
438
+ end
439
+ end
440
+
441
+ get '/api/datasets' do
442
+ dataset_uris = @rr.query(
443
+ RDF::Statement.new(nil, RDF.type, RDF::VOID.Dataset)
444
+ ).map { |statement|
445
+ statement.subject
446
+ }.to_a
447
+ halt 404 if dataset_uris.empty?
448
+
449
+ dataset_collection = dataset_uris.map { |uri|
450
+ {
451
+ :dataset => retrieve_dataset(uri),
452
+ :_links => {
453
+ :self => {
454
+ :type => 'dataset',
455
+ :href => uri.to_s
456
+ },
457
+ :evidence_collection => {
458
+ :type => 'evidence_collection',
459
+ :href => "#{uri}/evidence"
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+ status 200
466
+ render_json({ :dataset_collection => dataset_collection })
467
+ end
468
+
469
+ delete '/api/datasets/:id' do
470
+ id = params[:id]
471
+ void_dataset_uri = RDF::URI("#{base_url}/api/datasets/#{id}")
472
+ halt 404 unless dataset_exists?(void_dataset_uri)
473
+
474
+ dataset = retrieve_dataset(void_dataset_uri)
475
+ @api.delete_dataset(dataset[:identifier])
476
+ @rr.delete_statement(RDF::Statement.new(void_dataset_uri, nil, nil))
477
+
478
+ status 202
479
+ end
480
+
481
+ delete '/api/datasets' do
482
+ datasets = @rr.query(
483
+ RDF::Statement.new(nil, RDF.type, RDF::VOID.Dataset)
484
+ ).map { |stmt|
485
+ stmt.subject
486
+ }.to_a
487
+ halt 404 if datasets.empty?
488
+
489
+ datasets.each do |void_dataset_uri|
490
+ dataset = retrieve_dataset(void_dataset_uri)
491
+ @api.delete_dataset(dataset[:identifier])
492
+ @rr.delete_statement(RDF::Statement.new(void_dataset_uri, nil, nil))
493
+ end
494
+
495
+ status 202
496
+ end
497
+
498
+ private
499
+
500
+ unless self.methods.include?(:generate_uuid)
501
+
502
+ # Dynamically defines an efficient UUID method for the current ruby.
503
+ if RUBY_ENGINE =~ /^jruby/i
504
+ java_import 'java.util.UUID'
505
+ define_method(:generate_uuid) do
506
+ Java::JavaUtil::UUID.random_uuid.to_s
507
+ end
508
+ else
509
+ require 'uuid'
510
+ define_method(:generate_uuid) do
511
+ UUID.generate
512
+ end
513
+ end
514
+ end
515
+ end
516
+ end
517
+ end
518
+ # vim: ts=2 sw=2:
519
+ # encoding: utf-8