Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add an API endpoint to resolve an ontology URI in diffrent formats #69

Merged
merged 15 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
openjdk-11-jre-headless \
raptor2-utils \
wait-for-it \
libraptor2-dev \
&& rm -rf /var/lib/apt/lists/*

RUN mkdir -p /srv/ontoportal/ontologies_api
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ gem 'sinatra-advanced-routes'
gem 'sinatra-contrib', '~> 1.0'
gem 'request_store'
gem 'parallel'
gem 'json-ld'


# Rack middleware
gem 'ffi'
Expand Down
16 changes: 10 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ GIT

GIT
remote: https://github.com/ontoportal-lirmm/goo.git
revision: 6c51346b6f150a69391794b7909b30592acbbe0e
revision: 3f8b1f0b62c4334306f9ed5cb7b17a1b645e7db3
branch: development
specs:
goo (0.0.2)
Expand Down Expand Up @@ -57,7 +57,7 @@ GIT

GIT
remote: https://github.com/ontoportal-lirmm/ontologies_linked_data.git
revision: d37aeafbd7bef120917fb4d601f8287a9a859f69
revision: 337dce98ec27627d14a440ff2a6ed09483cdac12
branch: development
specs:
ontologies_linked_data (0.0.1)
Expand Down Expand Up @@ -117,7 +117,7 @@ GEM
bcrypt_pbkdf (1.1.0)
bigdecimal (1.4.2)
builder (3.2.4)
capistrano (3.18.0)
capistrano (3.18.1)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
Expand Down Expand Up @@ -191,12 +191,12 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-cloud-core (1.6.1)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.3.1)
google-cloud-errors (1.4.0)
google-protobuf (3.25.3-x86_64-darwin)
google-protobuf (3.25.3-x86_64-linux)
googleapis-common-protos (1.5.0)
Expand Down Expand Up @@ -230,13 +230,16 @@ GEM
i18n (0.9.5)
concurrent-ruby (~> 1.0)
json (2.7.1)
json-ld (3.0.2)
multi_json (~> 1.12)
rdf (>= 2.2.8, < 4.0)
json-schema (2.8.1)
addressable (>= 2.4)
json_pure (2.7.1)
jwt (2.8.1)
base64
kgio (2.11.4)
libxml-ruby (5.0.2)
libxml-ruby (5.0.3)
link_header (0.0.8)
logger (1.6.0)
macaddr (1.7.2)
Expand Down Expand Up @@ -422,6 +425,7 @@ DEPENDENCIES
ffi
goo!
haml (~> 5.2.2)
json-ld
json-schema (~> 2.0)
minitest (~> 4.0)
minitest-stub_any_instance
Expand Down
4 changes: 2 additions & 2 deletions bin/ontoportal
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ build_docker_run_cmd() {
local goo_path="$3"
local sparql_client_path="$4"

local docker_run_cmd="docker compose run --rm -it"
local docker_run_cmd="docker compose -p ontoportal_docker run --rm -it --name api-service"
local bash_cmd=""

# Conditionally add bind mounts only if the paths are not empty
Expand Down Expand Up @@ -177,7 +177,7 @@ run_command() {
dev() {
echo "Starting OntoPortal API development server..."

local custom_command="bundle exec shotgun --host 0.0.0.0 --env=development"
local custom_command="bundle exec shotgun --host 0.0.0.0 --env=development --port 9393"
run_command "$custom_command" "$@"
}

Expand Down
59 changes: 59 additions & 0 deletions controllers/dereference_resource_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require_relative '../test/test_case'


class DereferenceResourceController < ApplicationController
namespace "/ontologies" do
get "/:acronym/resolve/:uri" do
acronym = params[:acronym]
uri = params[:uri]

if acronym.blank? || uri.blank?
error 500, "Usage: ontologies/:acronym/resolve/:uri?output_format= OR POST: acronym, uri, output_format parameters"

Check warning on line 11 in controllers/dereference_resource_controller.rb

View check run for this annotation

Codecov / codecov/patch

controllers/dereference_resource_controller.rb#L11

Added line #L11 was not covered by tests
end

output_format = params[:output_format].presence || 'jsonld'
process_request(acronym, uri, output_format)
end

private

def process_request(acronym_param, uri_param, output_format)
acronym = acronym_param
uri = URI.decode_www_form_component(uri_param)

error 500, "INVALID URI" unless valid_url?(uri)
sub = LinkedData::Models::Ontology.find(acronym).first&.latest_submission

error 500, "Ontology not found" unless sub

r = Resource.new(sub.id, uri)
case output_format
when 'jsonld'
content_type 'application/json'
reply JSON.parse(r.to_json)

Check warning on line 33 in controllers/dereference_resource_controller.rb

View check run for this annotation

Codecov / codecov/patch

controllers/dereference_resource_controller.rb#L32-L33

Added lines #L32 - L33 were not covered by tests
when 'json'
content_type 'application/json'
reply JSON.parse(r.to_json)
when 'xml'
content_type 'application/xml'
reply r.to_xml
when 'turtle'
content_type 'text/turtle'
reply r.to_turtle
when 'ntriples'
content_type 'application/n-triples'
reply r.to_ntriples
else
error 500, "Invalid output format"

Check warning on line 47 in controllers/dereference_resource_controller.rb

View check run for this annotation

Codecov / codecov/patch

controllers/dereference_resource_controller.rb#L47

Added line #L47 was not covered by tests
end

end

def valid_url?(url)
uri = URI.parse(url)
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
rescue URI::InvalidURIError
false

Check warning on line 56 in controllers/dereference_resource_controller.rb

View check run for this annotation

Codecov / codecov/patch

controllers/dereference_resource_controller.rb#L56

Added line #L56 was not covered by tests
end
end
end
4 changes: 1 addition & 3 deletions helpers/users_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ def send_reset_token(email, username)
error 404, "User not found" unless user
reset_token = token(36)
user.resetToken = reset_token

return user if user.valid?


user.save(override_security: true)
LinkedData::Utils::Notifications.reset_password(user, reset_token)
user
Expand Down
2 changes: 2 additions & 0 deletions models/simple_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@
ProvisionalRelation = LinkedData::Models::ProvisionalRelation

SearchHelper = Sinatra::Helpers::SearchHelper

Resource = LinkedData::Models::Resource
191 changes: 191 additions & 0 deletions test/controllers/test_dereference_resource_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
require_relative '../test_case'
require 'rexml/document'

class TestDereferenceResourceController < TestCase

def self.before_suite
LinkedData::SampleData::Ontology.create_ontologies_and_submissions({
process_submission: true,
process_options: { process_rdf: true, extract_metadata: false, generate_missing_labels: false},
acronym: 'INRAETHESDEREF',
name: 'INRAETHES',
file_path: './test/data/ontology_files/thesaurusINRAE_nouv_structure.rdf',
ont_count: 1,
ontology_format: 'SKOS',
submission_count: 1
})

@@graph = "INRAETHESDEREF-0"
@@uri = CGI.escape("http://opendata.inrae.fr/thesaurusINRAE/c_6496")
end

def test_dereference_resource_controller_json
get "/ontologies/#{@@graph}/resolve/#{@@uri}?output_format=json"
assert last_response.ok?

result = last_response.body
expected_result = <<-JSON
{
"@context": {
"ns0": "http://opendata.inrae.fr/thesaurusINRAE/",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"owl": "http://www.w3.org/2002/07/owl#",
"skos": "http://www.w3.org/2004/02/skos/core#"
},
"@graph": [
{
"@id": "ns0:c_6496",
"@type": [
"owl:NamedIndividual",
"skos:Concept"
],
"skos:broader": {
"@id": "ns0:c_a9d99f3a"
},
"skos:topConceptOf": {
"@id": "ns0:mt_65"
},
"skos:inScheme": [
{
"@id": "ns0:thesaurusINRAE"
},
{
"@id": "ns0:mt_65"
}
],
"skos:prefLabel": {
"@value": "altération de l'ADN",
"@language": "fr"
}
},
{
"@id": "ns0:mt_65",
"skos:hasTopConcept": {
"@id": "ns0:c_6496"
}
}
]
}
JSON
a = sort_nested_hash(JSON.parse(result))
b = sort_nested_hash(JSON.parse(expected_result))
assert_equal b, a
end

def test_dereference_resource_controller_xml
get "/ontologies/#{@@graph}/resolve/#{@@uri}?output_format=xml"
assert last_response.ok?

result = last_response.body

expected_result_1 = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:ns0="http://opendata.inrae.fr/thesaurusINRAE/" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns:skos="http://www.w3.org/2004/02/skos/core#">
<owl:NamedIndividual rdf:about="http://opendata.inrae.fr/thesaurusINRAE/c_6496">
<rdf:type rdf:resource="http://www.w3.org/2004/02/skos/core#Concept"/>
<skos:broader rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/c_a9d99f3a"/>
<skos:topConceptOf rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/mt_65"/>
<skos:inScheme rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/thesaurusINRAE"/>
<skos:inScheme rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/mt_65"/>
<skos:prefLabel xml:lang="fr">altération de l'ADN</skos:prefLabel>
</owl:NamedIndividual>
<rdf:Description rdf:about="http://opendata.inrae.fr/thesaurusINRAE/mt_65">
<skos:hasTopConcept rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/c_6496"/>
</rdf:Description>
</rdf:RDF>
XML

expected_result_2 = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:ns0="http://opendata.inrae.fr/thesaurusINRAE/" xmlns:skos="http://www.w3.org/2004/02/skos/core#" xmlns:owl="http://www.w3.org/2002/07/owl#">
<skos:Concept rdf:about="http://opendata.inrae.fr/thesaurusINRAE/c_6496">
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#NamedIndividual"/>
<skos:inScheme rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/thesaurusINRAE"/>
<skos:inScheme rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/mt_65"/>
<skos:prefLabel xml:lang="fr">altération de l'ADN</skos:prefLabel>
<skos:topConceptOf rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/mt_65"/>
<skos:broader rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/c_a9d99f3a"/>
</skos:Concept>
<rdf:Description rdf:about="http://opendata.inrae.fr/thesaurusINRAE/mt_65">
<skos:hasTopConcept rdf:resource="http://opendata.inrae.fr/thesaurusINRAE/c_6496"/>
</rdf:Description>
</rdf:RDF>
XML


clean_xml = -> (x) { x.strip.gsub('/>', '').gsub('</', '').gsub('<', '').gsub('>', '').split(' ').reject(&:empty?)}


a = result.gsub('\\"', '"')[1..-2].split("\\n").map{|x| clean_xml.call(x)}.flatten
b_1 = expected_result_1.split("\n").map{|x| clean_xml.call(x)}.flatten
b_2 = expected_result_2.split("\n").map{|x| clean_xml.call(x)}.flatten

assert_includes [b_1.sort, b_2.sort], a.sort
end

def test_dereference_resource_controller_ntriples
get "/ontologies/#{@@graph}/resolve/#{@@uri}?output_format=ntriples"
assert last_response.ok?

result = last_response.body
expected_result = <<-NTRIPLES
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#NamedIndividual> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2004/02/skos/core#Concept> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/2004/02/skos/core#broader> <http://opendata.inrae.fr/thesaurusINRAE/c_a9d99f3a> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/2004/02/skos/core#topConceptOf> <http://opendata.inrae.fr/thesaurusINRAE/mt_65> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/2004/02/skos/core#inScheme> <http://opendata.inrae.fr/thesaurusINRAE/thesaurusINRAE> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/2004/02/skos/core#inScheme> <http://opendata.inrae.fr/thesaurusINRAE/mt_65> .
<http://opendata.inrae.fr/thesaurusINRAE/c_6496> <http://www.w3.org/2004/02/skos/core#prefLabel> "alt\\\\u00E9rationdel'ADN"@fr .
<http://opendata.inrae.fr/thesaurusINRAE/mt_65> <http://www.w3.org/2004/02/skos/core#hasTopConcept> <http://opendata.inrae.fr/thesaurusINRAE/c_6496> .
NTRIPLES
a = result.gsub('\\"', '"').gsub(' ', '')[1..-2].split("\\n").reject(&:empty?)
b = expected_result.gsub(' ', '').split("\n").reject(&:empty?)
assert_equal b.sort, a.sort
end

def test_dereference_resource_controller_turtle
get "/ontologies/#{@@graph}/resolve/#{@@uri}?output_format=turtle"
assert last_response.ok?

result = last_response.body
expected_result = <<-TURTLE
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix ns0: <http://opendata.inrae.fr/thesaurusINRAE/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .

ns0:c_6496
a owl:NamedIndividual, skos:Concept ;
skos:broader ns0:c_a9d99f3a ;
skos:inScheme ns0:mt_65, ns0:thesaurusINRAE ;
skos:prefLabel "altération de l'ADN"@fr ;
skos:topConceptOf ns0:mt_65 .

ns0:mt_65
skos:hasTopConcept ns0:c_6496 .
TURTLE
a = result.gsub('\\"', '"').gsub(' ', '')[1..-2].split("\\n").reject(&:empty?)
b = expected_result.gsub(' ', '').split("\n").reject(&:empty?)

assert_equal b.sort, a.sort
end

private

def sort_nested_hash(hash)
sorted_hash = {}

hash.each do |key, value|
if value.is_a?(Hash)
sorted_hash[key] = sort_nested_hash(value)
elsif value.is_a?(Array)
sorted_hash[key] = value.map { |item| item.is_a?(Hash) ? sort_nested_hash(item) : item }.sort_by { |item| item.to_s }
else
sorted_hash[key] = value
end
end

sorted_hash.sort.to_h
end

end
Loading