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