sadi-rb 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -0
- data/README.md +39 -23
- data/Rakefile +1 -1
- data/lib/sadi-rb.rb +3 -0
- data/lib/sadi-rb/asynchronous_service.rb +66 -0
- data/lib/sadi-rb/base_service.rb +71 -0
- data/lib/sadi-rb/example_service_async.rb +91 -0
- data/lib/sadi-rb/server.rb +41 -3
- data/lib/sadi-rb/services.rb +1 -2
- data/lib/sadi-rb/synchronous_service.rb +1 -47
- data/sadi-rb.gemspec +6 -2
- data/spec/server_spec.rb +148 -4
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 710664e9e7b3fd1fa404d9789934fbaab1ca6761
|
4
|
+
data.tar.gz: fa4892f551d5f15234837a89edf87798e48656cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e0dfb9db33fcb89220ba2d7dc7a5633510f6517728345342186f70fa933b6bc9a42c3ca030aa10043c38e0f6aff926a92c455e9bdf37fe92aa71b4223f37d7c
|
7
|
+
data.tar.gz: da6d8beb97e317df3e6677b7464abcd957f81f951fbf3acc765ce5d6547668f8b82db45c56b298f61ec2b2527ba338ec69a532edc7dfdbcfb81dcfb9cd2c68fe
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# sadi-rb
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/wstrinz/sadi-rb.png?branch=master)](https://travis-ci.org/wstrinz/sadi-rb)
|
4
|
+
|
3
5
|
### Installation
|
4
6
|
`gem install sadi-rb`
|
5
7
|
|
@@ -7,13 +9,13 @@ or clone the repository and run `rake install` for the latest version
|
|
7
9
|
|
8
10
|
### Description
|
9
11
|
|
10
|
-
Write [SADI] Services in Ruby, then host them
|
12
|
+
Write [SADI] Services in Ruby, using [ruby-rdf], then host them in a [Sinatra] application.
|
11
13
|
|
12
|
-
Currently only
|
14
|
+
Currently only support for [synchronous] services are fully supported, although an experimental [asynchronous] class is available.
|
13
15
|
|
14
16
|
### Usage
|
15
17
|
|
16
|
-
To test the server, use the gem's executable `sadi-rb`. This will start the server on [http://localhost:4567](http://localhost:4567), and load the [demo service].
|
18
|
+
To test the server, use the gem's executable `sadi-rb`. This will start the server on [http://localhost:4567](http://localhost:4567), and load the [demo service], which is based on the service at [http://sadiframework.org/examples/hello](http://sadiframework.org/examples/hello).
|
17
19
|
|
18
20
|
The server can also be run as part of a script using
|
19
21
|
|
@@ -25,35 +27,45 @@ SADI::Server.run!
|
|
25
27
|
|
26
28
|
### Writing your own services
|
27
29
|
|
28
|
-
|
30
|
+
#### Synchronous
|
31
|
+
|
32
|
+
To create a [synchronous] service, simply extend the `SADI::SynchronousService` module and implement the interface methods it requires;
|
29
33
|
|
30
34
|
```ruby
|
31
35
|
require 'sadi-rb'
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
+
class MyService
|
38
|
+
extend SADI::SynchronousService
|
39
|
+
|
40
|
+
def self.service_name
|
41
|
+
"my_service_name" # => service will be accessible at "/services/my_service_name"
|
42
|
+
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
def self.service_description
|
45
|
+
# an RDF::Graph of your service's description
|
46
|
+
end
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
48
|
+
def self.service_owl
|
49
|
+
# an RDF::Graph of your service's OWL classes
|
50
|
+
end
|
45
51
|
|
46
|
-
|
47
|
-
|
52
|
+
def self.process_object(input_graph, owl_object)
|
53
|
+
# Service logic goes here
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
55
|
+
# Should return an RDF::Graph of
|
56
|
+
# the output for the resource specified by the URI owl_object,
|
57
|
+
# from the RDF::Graph input_graph
|
58
|
+
end
|
53
59
|
end
|
54
60
|
```
|
55
61
|
|
56
|
-
Although SADI can theoretically use any vocabulary for its service description, the gem internals currently require that you use the [mygrid ontology] in implementing your `service_description` method
|
62
|
+
Although SADI can theoretically use any vocabulary for its service description, the gem internals currently require that you use the [mygrid ontology] in implementing your `service_description` method.
|
63
|
+
|
64
|
+
You also have access to the `parse_string(string, format)` method, inherited from [SADI::Converter](https://github.com/wstrinz/sadi-rb/blob/master/lib/sadi-rb/converter.rb) through `SADI::SynchronousService`, which can be used create a RDF::Graph from a serialized string. The `format` argument should be a symbol for an `RDF::Format` class, for example `:ttl` or `:rdfxml`.
|
65
|
+
|
66
|
+
#### Asynchronous Services
|
67
|
+
|
68
|
+
SADI also supports [asynchronous] services, which can be polled repeatedly until their results are available. To make a service asynchronous, you can simply extend the `SADI::AsynchronousService` module, and implement your service the same way you would a synchronous service. However beware that the implementation of asynchronous services is currently not thread safe or very well tested, so it may not work well on unless you're using MRI and Thin.
|
57
69
|
|
58
70
|
## Contributing to sadi-rb
|
59
71
|
|
@@ -70,6 +82,10 @@ Although SADI can theoretically use any vocabulary for its service description,
|
|
70
82
|
Copyright (c) 2013 Will Strinz. See LICENSE.txt for
|
71
83
|
further details.
|
72
84
|
|
73
|
-
[
|
85
|
+
[synchronous]: http://sadiframework.org/content/how-sadi-works/synchronous-sadi-services/
|
86
|
+
[asynchronous]: https://code.google.com/p/sadi/wiki/AsynchronousServices
|
87
|
+
[demo service]: https://github.com/wstrinz/sadi-rb/blob/master/lib/sadi-rb/example_service.rb
|
74
88
|
[SADI]: http://sadiframework.org
|
75
|
-
[mygrid ontology]: http://www.mygrid.org.uk/tools/service-management/mygrid-ontology/
|
89
|
+
[mygrid ontology]: http://www.mygrid.org.uk/tools/service-management/mygrid-ontology/
|
90
|
+
[ruby-rdf]: http://ruby-rdf.github.io/
|
91
|
+
[Sinatra]: http://www.sinatrarb.com/
|
data/Rakefile
CHANGED
@@ -22,7 +22,7 @@ Jeweler::Tasks.new do |gem|
|
|
22
22
|
gem.email = "wstrinz@gmail.com"
|
23
23
|
# gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
24
|
gem.authors = ["Will Strinz"]
|
25
|
-
gem.version = '0.0.
|
25
|
+
gem.version = '0.0.3'
|
26
26
|
# dependencies defined in Gemfile
|
27
27
|
end
|
28
28
|
Jeweler::RubygemsDotOrgTasks.new
|
data/lib/sadi-rb.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require_relative 'sadi-rb/server.rb'
|
2
2
|
require_relative 'sadi-rb/converter.rb'
|
3
|
+
require_relative 'sadi-rb/base_service.rb'
|
3
4
|
require_relative 'sadi-rb/synchronous_service.rb'
|
5
|
+
require_relative 'sadi-rb/asynchronous_service.rb'
|
4
6
|
require_relative 'sadi-rb/services.rb'
|
5
7
|
|
6
8
|
require_relative 'sadi-rb/example_service.rb'
|
9
|
+
require_relative 'sadi-rb/example_service_async.rb'
|
7
10
|
|
8
11
|
SADI.reload_services
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module SADI
|
2
|
+
module AsynchronousService
|
3
|
+
include SADI::Converter
|
4
|
+
include SADI::BaseService
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
@classes ||= []
|
8
|
+
@classes << base
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.extended(base)
|
12
|
+
@classes ||= []
|
13
|
+
@classes << base
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.classes
|
17
|
+
@classes
|
18
|
+
end
|
19
|
+
|
20
|
+
def jobs
|
21
|
+
# Yay not thread safety!
|
22
|
+
|
23
|
+
@jobs ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_job_id
|
27
|
+
# TODO better poll URLs
|
28
|
+
|
29
|
+
# "#{service_name}_#{Time.now.nsec.to_s(32)}"
|
30
|
+
Time.now.nsec.to_s(32)
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_input(input, format, poll_base)
|
34
|
+
job_id = generate_job_id
|
35
|
+
|
36
|
+
raise "Job already exists (#{job_id})" if jobs[job_id]
|
37
|
+
|
38
|
+
jobs[job_id] = nil
|
39
|
+
|
40
|
+
Thread.new do
|
41
|
+
|
42
|
+
gr = RDF::Graph.new
|
43
|
+
in_graph = parse_string(input,format)
|
44
|
+
|
45
|
+
input_objects(in_graph).each do |obj|
|
46
|
+
gr << process_object(in_graph, obj)
|
47
|
+
end
|
48
|
+
|
49
|
+
jobs[job_id] = gr
|
50
|
+
end
|
51
|
+
|
52
|
+
gr = RDF::Graph.new
|
53
|
+
out_class = output_classes.first
|
54
|
+
input_objects(parse_string(input,format)).each do |obj|
|
55
|
+
gr << RDF::Statement.new(obj, RDF.type, out_class)
|
56
|
+
gr << RDF::Statement.new(obj, RDF::RDFS.isDefinedBy, RDF::URI("#{poll_base}/#{job_id}"))
|
57
|
+
end
|
58
|
+
|
59
|
+
gr
|
60
|
+
end
|
61
|
+
|
62
|
+
def poll(job_id)
|
63
|
+
jobs[job_id]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module SADI
|
2
|
+
module BaseService
|
3
|
+
include SADI::Converter
|
4
|
+
|
5
|
+
# def self.included(base)
|
6
|
+
# @classes ||= []
|
7
|
+
# @classes << base
|
8
|
+
# end
|
9
|
+
|
10
|
+
# def self.extended(base)
|
11
|
+
# @classes ||= []
|
12
|
+
# @classes << base
|
13
|
+
# end
|
14
|
+
|
15
|
+
# def self.classes
|
16
|
+
# @classes
|
17
|
+
# end
|
18
|
+
|
19
|
+
def process_input(input, format)
|
20
|
+
raise "Input processing should be implemented by service module"
|
21
|
+
end
|
22
|
+
|
23
|
+
def input_objects(graph)
|
24
|
+
input_classes.map do |cl|
|
25
|
+
solutions = RDF::Query.execute(graph) do
|
26
|
+
pattern [:obj, RDF.type, cl]
|
27
|
+
end
|
28
|
+
|
29
|
+
solutions.map(&:obj)
|
30
|
+
end.flatten
|
31
|
+
end
|
32
|
+
|
33
|
+
def input_classes
|
34
|
+
moby = RDF::Vocabulary.new('http://www.mygrid.org.uk/mygrid-moby-service#')
|
35
|
+
|
36
|
+
solutions = RDF::Query.execute(service_description) do
|
37
|
+
pattern [nil, moby.inputParameter, :param]
|
38
|
+
pattern [:param, moby.objectType, :in_class]
|
39
|
+
end
|
40
|
+
|
41
|
+
solutions.map(&:in_class)
|
42
|
+
end
|
43
|
+
|
44
|
+
def output_classes
|
45
|
+
moby = RDF::Vocabulary.new('http://www.mygrid.org.uk/mygrid-moby-service#')
|
46
|
+
|
47
|
+
solutions = RDF::Query.execute(service_description) do
|
48
|
+
pattern [nil, moby.outputParameter, :param]
|
49
|
+
pattern [:param, moby.objectType, :out_class]
|
50
|
+
end
|
51
|
+
|
52
|
+
solutions.map(&:out_class)
|
53
|
+
end
|
54
|
+
|
55
|
+
def service_description(service)
|
56
|
+
raise "Must implement a #service_description method returning an RDF::Graph or Repository"
|
57
|
+
end
|
58
|
+
|
59
|
+
def service_owl(service)
|
60
|
+
raise "Must implement a #service_owl method returning an RDF::Graph or Repository"
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_object(owl_graph, object)
|
64
|
+
raise "Must implement a #process_input that takes an RDF::Graph or Repository as input and returns a new one"
|
65
|
+
end
|
66
|
+
|
67
|
+
def service_name
|
68
|
+
raise "Must define a service name"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class ExampleServiceAsync
|
2
|
+
extend SADI::AsynchronousService
|
3
|
+
class << self
|
4
|
+
def service_name
|
5
|
+
"hello_async"
|
6
|
+
end
|
7
|
+
|
8
|
+
def service_description
|
9
|
+
str = <<-EOS
|
10
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
11
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
12
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
13
|
+
|
14
|
+
<http://sadiframework.org/examples/hello> a <http://www.mygrid.org.uk/mygrid-moby-service#serviceDescription>;
|
15
|
+
rdfs:label "Hello, world"^^xsd:string;
|
16
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#hasOperation> [ a <http://www.mygrid.org.uk/mygrid-moby-service#operation>;
|
17
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#hasUnitTest> [ a <http://www.mygrid.org.uk/mygrid-moby-service#unitTest>;
|
18
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#exampleInput> <http://sadiframework.org/examples/t/hello.input.1.rdf>;
|
19
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#exampleOutput> <http://sadiframework.org/examples/t/hello.output.1.rdf>];
|
20
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#inputParameter> [ a <http://www.mygrid.org.uk/mygrid-moby-service#parameter>;
|
21
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#objectType> <http://sadiframework.org/examples/hello.owl#NamedIndividual>];
|
22
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#outputParameter> [ a <http://www.mygrid.org.uk/mygrid-moby-service#parameter>;
|
23
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#objectType> <http://sadiframework.org/examples/hello.owl#GreetedIndividual>]];
|
24
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#hasServiceDescriptionText> "A simple \"Hello, World\" service that reads a name and attaches a greeting."^^xsd:string;
|
25
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#hasServiceNameText> "Hello, world"^^xsd:string;
|
26
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#providedBy> [ a <http://www.mygrid.org.uk/mygrid-moby-service#organisation>;
|
27
|
+
<http://protege.stanford.edu/plugins/owl/dc/protege-dc.owl#creator> "info@sadiframework.org"^^xsd:string;
|
28
|
+
<http://www.mygrid.org.uk/mygrid-moby-service#authoritative> true];
|
29
|
+
rdfs:comment "A simple \"Hello, World\" service that reads a name and attaches a greeting."^^xsd:string .
|
30
|
+
EOS
|
31
|
+
|
32
|
+
parse_string(str, :ttl)
|
33
|
+
end
|
34
|
+
|
35
|
+
def service_owl
|
36
|
+
str = <<-EOS
|
37
|
+
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
38
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
39
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
40
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
41
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
42
|
+
|
43
|
+
<> a owl:Ontology .
|
44
|
+
|
45
|
+
<#GreetedIndividual> a owl:Class;
|
46
|
+
owl:equivalentClass [ a owl:Restriction;
|
47
|
+
owl:onProperty <#greeting>;
|
48
|
+
owl:someValuesFrom xsd:string] .
|
49
|
+
|
50
|
+
<#NamedIndividual> a owl:Class;
|
51
|
+
owl:equivalentClass [ a owl:Restriction;
|
52
|
+
owl:minCardinality "1"^^xsd:int;
|
53
|
+
owl:onProperty foaf:name] .
|
54
|
+
|
55
|
+
<#SecondaryParameters> a owl:Class;
|
56
|
+
owl:equivalentClass [ a owl:Restriction;
|
57
|
+
owl:minCardinality "1"^^xsd:int;
|
58
|
+
owl:onProperty <#lang>] .
|
59
|
+
|
60
|
+
<#greeting> a owl:DatatypeProperty .
|
61
|
+
|
62
|
+
<#lang> a owl:DatatypeProperty .
|
63
|
+
|
64
|
+
foaf:name a owl:DatatypeProperty;
|
65
|
+
rdfs:isDefinedBy foaf:index.rdf .
|
66
|
+
EOS
|
67
|
+
|
68
|
+
parse_string(str, :ttl)
|
69
|
+
end
|
70
|
+
|
71
|
+
def owl_prefix
|
72
|
+
"http://sadiframework.org/examples/hello.owl#"
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_object(in_graph, object)
|
76
|
+
out_graph = RDF::Graph.new
|
77
|
+
|
78
|
+
name = RDF::Query.execute(in_graph) do
|
79
|
+
pattern [object, RDF::FOAF.name, :name]
|
80
|
+
end
|
81
|
+
name = name.first.name
|
82
|
+
|
83
|
+
owl_vocab = RDF::Vocabulary.new(owl_prefix)
|
84
|
+
|
85
|
+
out_graph << RDF::Statement.new(object, RDF.type, output_classes.first)
|
86
|
+
out_graph << RDF::Statement.new(object, owl_vocab.greeting, "Hello, #{name}!")
|
87
|
+
|
88
|
+
out_graph
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/sadi-rb/server.rb
CHANGED
@@ -18,8 +18,13 @@ module SADI
|
|
18
18
|
get_description(params[:service])
|
19
19
|
end
|
20
20
|
|
21
|
+
|
21
22
|
post '/services/:service' do
|
22
|
-
|
23
|
+
handle_post(params[:service])
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/poll/:service/:job' do
|
27
|
+
poll_service(params[:service], params[:job])
|
23
28
|
end
|
24
29
|
|
25
30
|
get '/files/:service/:file' do
|
@@ -32,10 +37,15 @@ module SADI
|
|
32
37
|
repo
|
33
38
|
end
|
34
39
|
|
35
|
-
def
|
40
|
+
def handle_post(service)
|
36
41
|
svc = SADI.service_for(service)
|
37
42
|
raise "no service exists with name '#{service}'" unless svc
|
38
|
-
|
43
|
+
if svc.is_a? SynchronousService
|
44
|
+
rdf_response svc.process_input(request.body.read, request.content_type)
|
45
|
+
else
|
46
|
+
status 202
|
47
|
+
rdf_response svc.process_input(request.body.read, request.content_type, "http://#{request.host_with_port}/poll/#{svc.service_name}")
|
48
|
+
end
|
39
49
|
end
|
40
50
|
|
41
51
|
def get_description(service)
|
@@ -54,6 +64,34 @@ module SADI
|
|
54
64
|
.map{|f| "#{f.content_type} (#{f.to_sym})"}
|
55
65
|
.join('<br>')
|
56
66
|
end
|
67
|
+
|
68
|
+
def poll_service(service, job)
|
69
|
+
svc = SADI.service_for(service)
|
70
|
+
result = svc.poll(job)
|
71
|
+
if result.is_a?(RDF::Graph) || result.is_a?(RDF::Repository)
|
72
|
+
result
|
73
|
+
else
|
74
|
+
redirect_poll(svc, job)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def redirect_poll(svc, job)
|
79
|
+
status 302
|
80
|
+
retry_time = 10
|
81
|
+
headers \
|
82
|
+
"Pragma" => "sadi-please-wait = #{retry_time}",
|
83
|
+
"Location" => request.url
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
alias_method :base_run!, :run!
|
90
|
+
|
91
|
+
def run!
|
92
|
+
SADI.reload_services
|
93
|
+
base_run!
|
94
|
+
end
|
57
95
|
end
|
58
96
|
end
|
59
97
|
end
|
data/lib/sadi-rb/services.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module SADI
|
2
2
|
class << self
|
3
|
-
|
4
3
|
def service_for(name)
|
5
4
|
services[name]
|
6
5
|
end
|
@@ -16,7 +15,7 @@ module SADI
|
|
16
15
|
def reload_services
|
17
16
|
@@services = {}
|
18
17
|
|
19
|
-
SADI::SynchronousService.classes.each do |service|
|
18
|
+
(SADI::SynchronousService.classes | SADI::AsynchronousService.classes).each do |service|
|
20
19
|
if service.respond_to? 'service_name'
|
21
20
|
name = service.service_name
|
22
21
|
puts "Warning: service #{@@services[name]} already using name '#{name}', overwriting" if @@services[name]
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module SADI
|
2
2
|
module SynchronousService
|
3
3
|
include SADI::Converter
|
4
|
+
include SADI::BaseService
|
4
5
|
|
5
6
|
def self.included(base)
|
6
7
|
@classes ||= []
|
@@ -25,52 +26,5 @@ module SADI
|
|
25
26
|
|
26
27
|
gr
|
27
28
|
end
|
28
|
-
|
29
|
-
def input_objects(graph)
|
30
|
-
cl = input_classes.first
|
31
|
-
solutions = RDF::Query.execute(graph) do
|
32
|
-
pattern [:obj, RDF.type, cl]
|
33
|
-
end
|
34
|
-
|
35
|
-
solutions.map(&:obj)
|
36
|
-
end
|
37
|
-
|
38
|
-
def input_classes
|
39
|
-
moby = RDF::Vocabulary.new('http://www.mygrid.org.uk/mygrid-moby-service#')
|
40
|
-
|
41
|
-
solutions = RDF::Query.execute(service_description) do
|
42
|
-
pattern [nil, moby.inputParameter, :param]
|
43
|
-
pattern [:param, moby.objectType, :in_class]
|
44
|
-
end
|
45
|
-
|
46
|
-
solutions.map(&:in_class)
|
47
|
-
end
|
48
|
-
|
49
|
-
def output_classes
|
50
|
-
moby = RDF::Vocabulary.new('http://www.mygrid.org.uk/mygrid-moby-service#')
|
51
|
-
|
52
|
-
solutions = RDF::Query.execute(service_description) do
|
53
|
-
pattern [nil, moby.outputParameter, :param]
|
54
|
-
pattern [:param, moby.objectType, :out_class]
|
55
|
-
end
|
56
|
-
|
57
|
-
solutions.map(&:out_class)
|
58
|
-
end
|
59
|
-
|
60
|
-
def service_description(service)
|
61
|
-
raise "Must implement a #service_description method returning an RDF::Graph or Repository"
|
62
|
-
end
|
63
|
-
|
64
|
-
def service_owl(service)
|
65
|
-
raise "Must implement a #service_owl method returning an RDF::Graph or Repository"
|
66
|
-
end
|
67
|
-
|
68
|
-
def process_object(owl_graph, object)
|
69
|
-
raise "Must implement a #process_input that takes an RDF::Graph or Repository as input and returns a new one"
|
70
|
-
end
|
71
|
-
|
72
|
-
def service_name
|
73
|
-
raise "Must define a service name"
|
74
|
-
end
|
75
29
|
end
|
76
30
|
end
|
data/sadi-rb.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "sadi-rb"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Will Strinz"]
|
12
|
-
s.date = "2013-10-
|
12
|
+
s.date = "2013-10-17"
|
13
13
|
s.description = "Build and run SADI services with ruby-rdf and sinatra"
|
14
14
|
s.email = "wstrinz@gmail.com"
|
15
15
|
s.executables = ["sadi-rb"]
|
@@ -19,14 +19,18 @@ Gem::Specification.new do |s|
|
|
19
19
|
]
|
20
20
|
s.files = [
|
21
21
|
".document",
|
22
|
+
".travis.yml",
|
22
23
|
"Gemfile",
|
23
24
|
"LICENSE.txt",
|
24
25
|
"README.md",
|
25
26
|
"Rakefile",
|
26
27
|
"bin/sadi-rb",
|
27
28
|
"lib/sadi-rb.rb",
|
29
|
+
"lib/sadi-rb/asynchronous_service.rb",
|
30
|
+
"lib/sadi-rb/base_service.rb",
|
28
31
|
"lib/sadi-rb/converter.rb",
|
29
32
|
"lib/sadi-rb/example_service.rb",
|
33
|
+
"lib/sadi-rb/example_service_async.rb",
|
30
34
|
"lib/sadi-rb/server.rb",
|
31
35
|
"lib/sadi-rb/services.rb",
|
32
36
|
"lib/sadi-rb/synchronous_service.rb",
|
data/spec/server_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe SADI::Server do
|
|
43
43
|
|
44
44
|
it "returns output on post" do
|
45
45
|
header "Accept", "text/turtle"
|
46
|
-
header "Content-
|
46
|
+
header "Content-type", "application/rdf+xml"
|
47
47
|
|
48
48
|
post '/services/hello', sample_input
|
49
49
|
|
@@ -54,16 +54,160 @@ describe SADI::Server do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
context "content negotiation" do
|
57
|
-
|
57
|
+
['application/rdf+xml'].each do |format|
|
58
|
+
describe "accepts #{format} (JRuby issues)", no_travis: true do
|
59
|
+
it do
|
60
|
+
header "Accept", format
|
61
|
+
get '/services/hello'
|
62
|
+
|
63
|
+
last_response.should be_ok
|
64
|
+
last_response.content_type.should == format
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
%w{text/turtle application/ld+json application/json}.each do |format|
|
58
70
|
describe "accepts #{format}" do
|
59
|
-
it
|
71
|
+
it do
|
60
72
|
header "Accept", format
|
61
73
|
get '/services/hello'
|
62
74
|
|
63
75
|
last_response.should be_ok
|
64
76
|
last_response.content_type.should == format
|
65
|
-
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "async services" do
|
83
|
+
it "basic get" do
|
84
|
+
get 'services/hello_async'
|
85
|
+
last_response.should be_ok
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "post" do
|
89
|
+
it "receives poll url" do
|
90
|
+
header "Accept", "text/turtle"
|
91
|
+
header "Content-type", "application/rdf+xml"
|
92
|
+
|
93
|
+
post '/services/hello_async', sample_input
|
94
|
+
last_response.status.should == 202
|
95
|
+
|
96
|
+
last_response.body["rdf-schema#isDefinedBy"].should_not be nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "gets result from poll url" do
|
100
|
+
header "Accept", "text/turtle"
|
101
|
+
header "Content-type", "application/rdf+xml"
|
102
|
+
|
103
|
+
post '/services/hello_async', sample_input
|
104
|
+
|
105
|
+
last_response.status.should == 202
|
106
|
+
|
107
|
+
poll_id = last_response.body.scan(%r{http://example.org/poll/hello_async/(\w+)}).first.first
|
108
|
+
|
109
|
+
get "/poll/hello_async/#{poll_id}"
|
110
|
+
|
111
|
+
last_response.should be_ok
|
112
|
+
last_response.body["Hello, Guy Incognito"].should_not be nil
|
113
|
+
end
|
114
|
+
|
115
|
+
context "redirection" do
|
116
|
+
before :all do
|
117
|
+
@cl = Class.new do
|
118
|
+
extend SADI::AsynchronousService
|
119
|
+
|
120
|
+
def self.service_name
|
121
|
+
"my_async"
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.service_description
|
125
|
+
ExampleServiceAsync.service_description
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.service_owl
|
129
|
+
ExampleServiceAsync.service_owl
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.process_object(input_graph, owl_object)
|
133
|
+
sleep(1)
|
134
|
+
ExampleServiceAsync.process_object(input_graph, owl_object)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
SADI.reload_services
|
139
|
+
end
|
140
|
+
|
141
|
+
it "receives redirects" do
|
142
|
+
header "Accept", "text/turtle"
|
143
|
+
header "Content-type", "application/rdf+xml"
|
144
|
+
|
145
|
+
post '/services/my_async', sample_input
|
146
|
+
|
147
|
+
poll_id = last_response.body.scan(%r{http://example.org/poll/my_async/(\w+)}).first.first
|
148
|
+
|
149
|
+
get "/poll/my_async/#{poll_id}"
|
150
|
+
|
151
|
+
last_response.status.should == 302
|
152
|
+
# puts last_response.headers["Pragma"]
|
153
|
+
last_response.headers["Pragma"][/sadi-please-wait = \d+/].should_not be nil
|
154
|
+
last_response.headers["Location"].should == "http://example.org/poll/my_async/#{poll_id}"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "eventually receives results" do
|
158
|
+
header "Accept", "text/turtle"
|
159
|
+
header "Content-type", "application/rdf+xml"
|
160
|
+
|
161
|
+
post '/services/my_async', sample_input.gsub("Guy Incognito", "Nick Danger")
|
162
|
+
|
163
|
+
poll_id = last_response.body.scan(%r{http://example.org/poll/my_async/(\w+)}).first.first
|
164
|
+
|
165
|
+
get "/poll/my_async/#{poll_id}"
|
166
|
+
|
167
|
+
last_response.status.should == 302
|
168
|
+
|
169
|
+
sleep 1
|
170
|
+
|
171
|
+
get "/poll/my_async/#{poll_id}"
|
172
|
+
last_response.body["Hello, Nick Danger"].should_not be nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "define new classes" do
|
180
|
+
before do
|
181
|
+
@cl = Class.new do
|
182
|
+
extend SADI::SynchronousService
|
183
|
+
|
184
|
+
def self.service_name
|
185
|
+
"my_service_name" # => service will be accessible at "/services/my_service_name"
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.service_description
|
189
|
+
# an RDF::Graph of your service's description
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.service_owl
|
193
|
+
# an RDF::Graph of your service's OWL classes
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.process_object(input_graph, owl_object)
|
197
|
+
# Service logic goes here
|
198
|
+
|
199
|
+
# Should return an RDF::Graph of
|
200
|
+
# the output for the resource specified by the URI owl_object,
|
201
|
+
# from the RDF::Graph input_graph
|
202
|
+
end
|
66
203
|
end
|
204
|
+
|
205
|
+
SADI.reload_services
|
67
206
|
end
|
207
|
+
|
208
|
+
it {
|
209
|
+
get 'services/my_service_name'
|
210
|
+
last_response.should be_ok
|
211
|
+
}
|
68
212
|
end
|
69
213
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sadi-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Will Strinz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -174,14 +174,18 @@ extra_rdoc_files:
|
|
174
174
|
- README.md
|
175
175
|
files:
|
176
176
|
- .document
|
177
|
+
- .travis.yml
|
177
178
|
- Gemfile
|
178
179
|
- LICENSE.txt
|
179
180
|
- README.md
|
180
181
|
- Rakefile
|
181
182
|
- bin/sadi-rb
|
182
183
|
- lib/sadi-rb.rb
|
184
|
+
- lib/sadi-rb/asynchronous_service.rb
|
185
|
+
- lib/sadi-rb/base_service.rb
|
183
186
|
- lib/sadi-rb/converter.rb
|
184
187
|
- lib/sadi-rb/example_service.rb
|
188
|
+
- lib/sadi-rb/example_service_async.rb
|
185
189
|
- lib/sadi-rb/server.rb
|
186
190
|
- lib/sadi-rb/services.rb
|
187
191
|
- lib/sadi-rb/synchronous_service.rb
|