/* Part of ClioPatria SeRQL and SPARQL server
Author: Jan Wielemaker
E-mail: J.Wielemaker@vu.nl
WWW: http://www.swi-prolog.org
Copyright (c) 2010-2018, University of Amsterdam,
VU University Amsterdam
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(api_sesame,
[ api_action/4 % +Request, +Goal, +Format, +Message
]).
:- use_module(rdfql(serql)).
:- use_module(rdfql(sparql)).
:- use_module(rdfql(rdf_io)).
:- use_module(rdfql(rdf_html)).
:- use_module(library(http/http_parameters)).
:- use_module(user(user_db)).
:- use_module(library(semweb/rdfs)).
:- use_module(library(semweb/rdf_db)).
:- use_module(library(semweb/rdf_http_plugin)).
:- use_module(library(semweb/rdf_file_type)).
:- use_module(library(semweb/rdf_persistency)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_request_value)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/http_client)).
:- use_module(library(http/http_open)).
:- use_module(library(http/json)).
:- use_module(library(memfile)).
:- use_module(library(debug)).
:- use_module(library(lists)).
:- use_module(library(option)).
:- use_module(library(apply)).
:- use_module(library(settings)).
:- use_module(components(query)).
:- use_module(components(basics)).
:- use_module(components(messages)).
:- meta_predicate(api_action2(+,0,+,+)).
:- http_handler(sesame('login'), http_login, []).
:- http_handler(sesame('logout'), http_logout, []).
:- http_handler(sesame('evaluateQuery'), evaluate_query,
[spawn(sparql_query)]).
:- http_handler(sesame('evaluateGraphQuery'), evaluate_graph_query,
[spawn(sparql_query)]).
:- http_handler(sesame('evaluateTableQuery'), evaluate_table_query,
[spawn(sparql_query)]).
:- http_handler(sesame('extractRDF'), extract_rdf, []).
:- http_handler(sesame('listRepositories'), list_repositories, []).
:- http_handler(sesame('clearRepository'), clear_repository, []).
:- http_handler(sesame('unloadSource'), unload_source,
[ time_limit(infinite) ]).
:- http_handler(sesame('unloadGraph'), unload_graph,
[ time_limit(infinite) ]).
:- http_handler(sesame('uploadData'), upload_data,
[ time_limit(infinite) ]).
:- http_handler(sesame('uploadURL'), upload_url,
[ time_limit(infinite) ]).
:- http_handler(sesame('removeStatements'), remove_statements,
[ time_limit(infinite) ]).
:- http_handler(sesame('flushJournal'), flush_journal,
[ time_limit(infinite) ]).
:- http_handler(sesame('modifyPersistency'), modify_persistency,
[ time_limit(infinite) ]).
:- http_handler(sesame('addPrefix'), add_prefix, []).
:- http_handler(sesame('defPrefix'), del_prefix, []).
:- html_meta
api_action(+, 0, +, html).
%! http_login(+Request)
%
% HTTP handler to associate the current session with a local user.
% If the login succeeds a 200 reply according to the resultFormat
% parameters is sent. If the result fails due to a wrong
% user/password, the server responds with a 403 (forbidden)
% message. Other failures result in a 500 (server error).
%
% @see help('howto/ClientAuth.txt') for additional information on
% authetication.
http_login(Request) :-
http_parameters(Request,
[ user(User),
password(Password),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
api_action(Request,
( validate_login(Request, User, Password),
login(User)
),
ResultFormat,
'Login ~w'-[User]).
validate_login(_, User, Password) :-
validate_password(User, Password),
!.
validate_login(Request, _, _) :-
memberchk(path(Path), Request),
throw(http_reply(forbidden(Path))).
%! http_logout(+Request)
%
% HTTP handler to logout current user.
http_logout(Request) :-
http_parameters(Request,
[ resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
api_action(Request,
logout_user(Message),
ResultFormat,
Message).
logout_user('Logout ~w'-[User]) :-
logged_on(User),
!,
logout(User).
logout_user('Not logged on'-[]).
%! evaluate_query(+Request) is det.
%
% HTTP handler for both SeRQL and SPARQL queries. This handler
% deals with _interactive_ queries. Machines typically access
% /sparql/ to submit queries and process result compliant to the
% SPARQL protocol.
evaluate_query(Request) :-
http_parameters(Request,
[ repository(Repository),
query(Query),
queryLanguage(QueryLanguage),
resultFormat(ResultFormat),
serialization(Serialization),
resourceFormat(ResourceFormat),
entailment(Entailment),
storeAs(SaveAs)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
statistics(cputime, CPU0),
downcase_atom(QueryLanguage, QLang),
compile(QLang, Query, Compiled,
[ entailment(Entailment),
type(Type)
]),
authorized_query(Type, Repository, ResultFormat),
findall(Reply, run(QLang, Compiled, Reply), Result),
statistics(cputime, CPU1),
CPU is CPU1 - CPU0,
store_query(construct, SaveAs, Query),
( graph_type(Type)
-> write_graph(Result,
[ result_format(ResultFormat),
serialization(Serialization),
resource_format(ResourceFormat),
cputime(CPU)
])
; Type = select(VarNames)
-> write_table(Result,
[ variables(VarNames),
result_format(ResultFormat),
serialization(Serialization),
resource_format(ResourceFormat),
cputime(CPU)
])
; Type == ask, Result = [Reply]
-> reply_html_page(cliopatria(default),
title('ASK Result'),
[ h4('ASK query completed'),
p(['Answer = ', Reply])
])
; Type == update, Result = [Reply]
-> reply_html_page(cliopatria(default),
title('Update Result'),
[ h4('Update query completed'),
p(['Answer = ', Reply])
])
).
authorized_query(update, Repository, ResultFormat) :-
!,
authorized_api(write(Repository, sparql(update)), ResultFormat).
authorized_query(_, Repository, ResultFormat) :-
authorized_api(read(Repository, query), ResultFormat).
%! evaluate_graph_query(+Request)
%
% Handle CONSTRUCT queries.
evaluate_graph_query(Request) :-
http_parameters(Request,
[ repository(Repository),
query(Query),
queryLanguage(QueryLanguage),
resultFormat(ResultFormat),
serialization(Serialization),
resourceFormat(ResourceFormat),
entailment(Entailment),
storeAs(SaveAs)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(read(Repository, query), ResultFormat),
statistics(cputime, CPU0),
downcase_atom(QueryLanguage, QLang),
compile(QLang, Query, Compiled,
[ entailment(Entailment),
type(Type)
]),
( graph_type(Type)
-> true
; throw(error(domain_error(query_type(graph), Type), _))
),
findall(T, run(QLang, Compiled, T), Triples),
statistics(cputime, CPU1),
store_query(construct, SaveAs, Query),
CPU is CPU1 - CPU0,
write_graph(Triples,
[ result_format(ResultFormat),
serialization(Serialization),
resource_format(ResourceFormat),
cputime(CPU)
]).
graph_type(construct).
graph_type(describe).
%! evaluate_table_query(+Request)
%
% Handle SELECT queries.
evaluate_table_query(Request) :-
http_parameters(Request,
[ repository(Repository),
query(Query),
queryLanguage(QueryLanguage),
resultFormat(ResultFormat),
serialization(Serialization),
resourceFormat(ResourceFormat),
entailment(Entailment),
storeAs(SaveAs)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(read(Repository, query), ResultFormat),
statistics(cputime, CPU0),
downcase_atom(QueryLanguage, QLang),
compile(QLang, Query, Compiled,
[ entailment(Entailment),
type(select(VarNames))
]),
findall(R, run(QLang, Compiled, R), Rows),
statistics(cputime, CPU1),
CPU is CPU1 - CPU0,
store_query(select, SaveAs, Query),
write_table(Rows,
[ variables(VarNames),
result_format(ResultFormat),
serialization(Serialization),
resource_format(ResourceFormat),
cputime(CPU)
]).
%! compile(+Language, +Query, -Compiled, +Options)
%
% Compile a query and validate the query-type
compile(serql, Query, Compiled, Options) :-
!,
serql_compile(Query, Compiled, Options).
compile(sparql, Query, Compiled, Options) :-
!,
sparql_compile(Query, Compiled, Options).
compile(Language, _, _, _) :-
throw(error(domain_error(query_language, Language), _)).
%! run(+Language, +Compiled, -Reply)
run(serql, Compiled, Reply) :-
serql_run(Compiled, Reply).
run(sparql, Compiled, Reply) :-
sparql_run(Compiled, Reply).
%! extract_rdf(+Request)
%
% HTTP handler to extract RDF from the database. This handler
% separates the data into schema data and non-schema data, where
% schema data are triples whose subject is an rdfs:Class or
% rdf:Property. By default both are =off=, so one needs to pass
% either or both of the =schema= and =data= options as =on=.
extract_rdf(Request) :-
http_parameters(Request,
[ repository(Repository),
schema(Schema),
data(Data),
explicitOnly(ExplicitOnly),
niceOutput(_NiceOutput),
serialization(Serialization)
],
[ attribute_declarations(attribute_decl)
]),
authorized(read(Repository, download)),
statistics(cputime, CPU0),
findall(T, export_triple(Schema, Data, ExplicitOnly, T), Triples),
statistics(cputime, CPU1),
CPU is CPU1 - CPU0,
write_graph(Triples,
[ serialization(Serialization),
cputime(CPU)
]).
%! export_triple(+Schema, +Data, +ExplicitOnly, -RDF).
export_triple(off, off, _, _) :-
!,
fail. % no data requested
export_triple(on, on, on, rdf(S,P,O)) :-
!,
rdf_db:rdf(S,P,O).
export_triple(on, on, off, rdf(S,P,O)) :-
!,
rdfs_entailment:rdf(S,P,O).
export_triple(off, on, Explicit, RDF) :-
export_triple(on, on, Explicit, RDF),
\+ schema_triple(RDF).
export_triple(on, off, Explicit, RDF) :-
export_triple(on, on, Explicit, RDF),
schema_triple(RDF).
schema_triple(rdf(S,_P,_O)) :-
rdfs_individual_of(S, rdf:'Property').
schema_triple(rdf(S,_P,_O)) :-
rdfs_individual_of(S, rdfs:'Class').
%! list_repositories(+Request)
%
% List the available repositories. This is only =default= for now
list_repositories(_Request) :-
Repository = default,
logged_on(User, anonymous),
( catch(check_permission(User, write(Repository, _)), _, fail)
-> Write = true
; Write = false
),
( catch(check_permission(User, read(Repository, _)), _, fail)
-> Read = true
; Read = false
),
format('Content-type: text/xml~n~n'),
format('~n~n', []),
format('~n'),
format(' ~n',
[ Read, Write ]),
format(' Default repository~n'),
format(' ~n'),
format('~n').
%! clear_repository(+Request)
%
% Clear the repository.
clear_repository(Request) :-
http_parameters(Request,
[ repository(Repository),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, clear), ResultFormat),
api_action(Request,
rdf_reset_db,
ResultFormat,
'Clear database'-[]).
%! unload_source(+Request)
%
% Remove triples loaded from a specified source
unload_source(Request) :-
http_parameters(Request,
[ repository(Repository),
source(Source),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, unload(Source)), ResultFormat),
api_action(Request, rdf_unload(Source),
ResultFormat,
'Unload triples from ~w'-[Source]).
%! unload_graph(+Request)
%
% Remove a named graph.
unload_graph(Request) :-
http_parameters(Request,
[ repository(Repository),
graph(Graph, []),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, unload(Graph)), ResultFormat),
api_action(Request, rdf_unload_graph(Graph),
ResultFormat,
'Unload triples from ~w'-[Graph]).
%! flush_journal(+Request)
%
% Flush the journal of the requested graph
flush_journal(Request) :-
http_parameters(Request,
[ repository(Repository),
graph(Graph, []),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, unload(Graph)), ResultFormat),
api_action(Request, rdf_flush_journals([graph(Graph)]),
ResultFormat,
'Flushed journals for graph ~w'-[Graph]).
%! modify_persistency(+Request)
%
% Change the persistent properties for the requested graph
modify_persistency(Request) :-
http_parameters(Request,
[ repository(Repository),
graph(Graph, []),
resultFormat(ResultFormat),
persistent(Persistent)
],
[ attribute_declarations(attribute_decl)
]),
persistency(Persistent, PVal, Action),
result_format(Request, ResultFormat),
authorized_api(write(Repository, persistent(Graph)), ResultFormat),
api_action(Request, rdf_persistency(Graph, PVal),
ResultFormat,
'~w persistency for graph ~w'-[Action, Graph]).
persistency(on, true, 'Set').
persistency(off, false, 'Cleared').
%! upload_data(Request).
%
% Sesame compliant method to upload data to the repository,
% typically used to handle a POST-form from a web-browser (e.g.,
% _|Load local file|_ in the ClioPatria menu). If =dataFormat= is
% omitted, the format of the data is guessed from the data itself.
% Currently, this possitively identifies valid RDF/XML and assumes
% that anything else is Turtle.
:- if(current_predicate(http_convert_parameters/3)).
%! create_tmp_file(+Stream, -Out, +Options) is det.
%
% Called from library(http/http_multipart_plugin) to process
% uploaded file from a form.
%
% @arg Stream is the input stream. It signals EOF at the end of
% the part, but must *not* be closed.
% @arg Options provides information about the part. Typically,
% this contains filename(FileName) and optionally media(Type,
% MediaParams).
:- public create_tmp_file/3.
create_tmp_file(Stream, file(File, Options), Options) :-
setup_call_catcher_cleanup(
tmp_file_stream(binary, File, Out),
copy_stream_data(Stream, Out),
Why,
cleanup(Why, File, Out)).
cleanup(Why, File, Out) :-
close(Out),
( Why == exit
-> true
; catch(delete_file(File), _, true)
).
%! upload_data_file(+Request, +FormData, +TempFile, +FileOptions)
%
% Load RDF from TempFile with additional form data provided in
% FormData. Options are the options passed from the uploaded file
% and include filename(Name) and optionally media(Type, Params).
upload_data_file(Request, Data, TmpFile, FileOptions) :-
http_convert_parameters(Data,
[ repository(Repository),
dataFormat(DataFormat),
baseURI(BaseURI),
verifyData(_Verify),
resultFormat(ResultFormat)
],
attribute_decl),
result_format(Request, ResultFormat),
authorized_api(write(Repository, load(posted)), ResultFormat),
phrase(load_option(DataFormat, BaseURI), LoadOptions),
append(LoadOptions, FileOptions, Options),
api_action(Request,
setup_call_cleanup(
open(TmpFile, read, Stream),
rdf_guess_format_and_load(Stream, Options),
close(Stream)),
ResultFormat,
'Load data from POST'-[]).
upload_option(_=_) :- !.
upload_option(Term) :- functor(Term, _, 1).
upload_data(Request) :-
option(method(post), Request),
!,
http_read_data(Request, Data,
[ on_filename(create_tmp_file)
]),
( option(data(file(TmpFile, FileOptions)), Data)
-> true
; existence_error(attribute_declaration, data)
),
include(upload_option, FileOptions, Options),
call_cleanup(upload_data_file(Request, Data, TmpFile, Options),
catch(delete_file(TmpFile), _, true)).
:- endif.
upload_data(Request) :-
http_parameters(Request,
[ repository(Repository),
data(Data,
[ description('RDF data to be loaded')
]),
dataFormat(DataFormat),
baseURI(BaseURI),
verifyData(_Verify),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, load(posted)), ResultFormat),
phrase(load_option(DataFormat, BaseURI), Options),
atom_to_memory_file(Data, MemFile),
api_action(Request,
setup_call_cleanup(open_memory_file(MemFile, read, Stream),
rdf_guess_format_and_load(Stream, Options),
( close(Stream),
free_memory_file(MemFile)
)),
ResultFormat,
'Load data from POST'-[]).
%! upload_url(+Request)
%
% Load data from an HTTP server. This API is compatible to Sesame,
% although the =verifyData= option is not implemented (data is
% always checked for syntax). Unlike Sesame, the default format is
% not =rdfxml=, but derived from the Content-type reported by the
% server.
%
% @see Calls rdf_load/2 for the actual loading.
% @see load_url_form/1 a form to access this API
upload_url(Request) :-
http_parameters(Request,
[ url(URL, []),
dataFormat(DataFormat),
baseURI(BaseURI,
[ default(URL)
]),
resultFormat(ResultFormat),
verifyData(_Verify),
repository(Repository)
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
authorized_api(write(Repository, load(url(URL))), ResultFormat),
phrase(load_option(DataFormat, BaseURI), Options),
api_action(Request,
load_from_url(URL, Options),
ResultFormat,
'Load data from ~w'-[URL]).
load_from_url(URL, Options) :-
http_open(URL, In,
[ cert_verify_hook(ssl_verify)
]),
call_cleanup(rdf_guess_format_and_load(In, Options),
close(In)).
:- public ssl_verify/5.
%! ssl_verify(+SSL, +ProblemCert, +AllCerts, +FirstCert, +Error)
%
% Currently we accept all certificates. We organise our own
% security using SHA1 signatures, so we do not care about the
% source of the data.
ssl_verify(_SSL,
_ProblemCertificate, _AllCertificates, _FirstCertificate,
_Error).
load_option(DataFormat, BaseURI) -->
data_format_option(DataFormat),
base_uri_option(BaseURI).
data_format_option(Var) --> {var(Var)}, !.
data_format_option(rdfxml) --> [format(xml)].
data_format_option(ntriples) --> [format(turtle)].
data_format_option(turtle) --> [format(turtle)].
base_uri_option(Var) --> {var(Var)}, !.
base_uri_option(URI) --> [base_uri(URI)].
%! remove_statements(+Request)
%
% Remove statements from the database
remove_statements(Request) :-
http_parameters(Request,
[ repository(Repository, [optional(true)]),
resultFormat(ResultFormat),
% as documented
subject(Subject, [optional(true)]),
predicate(Predicate, [optional(true)]),
object(Object, [optional(true)]),
% remove (turtle) graph
baseURI(BaseURI),
dataFormat(DataFormat),
data(Data, [optional(true)])
],
[ attribute_declarations(attribute_decl)
]),
result_format(Request, ResultFormat),
instantiated(Subject, SI),
instantiated(Predicate, PI),
instantiated(Object, OI),
authorized_api(write(Repository, remove_statements(SI, PI, OI)),
ResultFormat),
( nonvar(Data)
-> setup_call_cleanup(( atom_to_memory_file(Data, MemFile),
open_memory_file(MemFile, read, Stream,
[ free_on_close(true)
])
),
( rdf_guess_data_format(Stream, DataFormat),
get_triples(stream(Stream),
Triples,
[ base_uri(BaseURI),
data_format(DataFormat)
])
),
close(Stream)),
length(Triples, NTriples),
debug(removeStatements, 'Removing ~D statements', [NTriples]),
api_action(Request,
remove_triples(Triples),
ResultFormat,
'Remove ~D triples'-[NTriples])
; debug(removeStatements, 'removeStatements = ~w',
[rdf(Subject, Predicate, Object)]),
ntriple_part(Subject, subject, S),
ntriple_part(Predicate, predicate, P),
ntriple_part(Object, object, O),
debug(removeStatements, 'Action = ~q', [rdf_retractall(S,P,O)]),
api_action(Request,
rdf_retractall(S,P,O),
ResultFormat,
'Remove statements from ~k'-[rdf(S,P,O)])
).
%! remove_triples(+List)
%
% Remove indicated triples from the database.
remove_triples([]).
remove_triples([rdf(S,P,O)|T]) :-
rdf_retractall(S,P,O),
remove_triples(T).
instantiated(X, I) :-
( var(X)
-> I = (-)
; I = (+)
).
ntriple_part(In, _, _) :-
var(In),
!.
ntriple_part('', _, _) :- !.
ntriple_part(In, Field, Out) :-
atom_codes(In, Codes),
phrase(rdf_ntriple_part(Field, Out), Codes),
!.
ntriple_part(Text, Field, _) :-
throw(error(type_error(ntriples(Field), Text),
context(_,
'Field must be in N-triples notation'))).
%! rdf_ntriple_part(+Type, -Value)//
%
% Parse one of the fields of an ntriple. This is used for the
% SWI-Prolog Sesame (rdf4j.org) implementation to realise
% /servlets/removeStatements. I do not think public use of this
% predicate should be stimulated.
rdf_ntriple_part(subject, Subject) -->
subject(Subject).
rdf_ntriple_part(predicate, Predicate) -->
predicate(Predicate).
rdf_ntriple_part(object, Object) -->
object(Object).
subject(Subject) -->
uniref(Subject),
!.
subject(Subject) -->
node_id(Subject).
predicate(Predicate) -->
uniref(Predicate).
object(Object) -->
uniref(Object),
!.
object(Object) -->
node_id(Object).
object(Object) -->
literal(Object).
uniref(URI) -->
"<",
escaped_uri_codes(Codes),
">",
!,
{ atom_codes(URI, Codes)
}.
node_id(node(Id)) --> % anonymous nodes
"_:",
name_start(C0),
name_codes(Codes),
{ atom_codes(Id, [C0|Codes])
}.
literal(Literal) -->
lang_string(Literal),
!.
literal(Literal) -->
xml_string(Literal).
% name_start(-Code)
% name_codes(-ListfCodes)
%
% Parse identifier names
name_start(C) -->
[C],
{ code_type(C, alpha)
}.
name_codes([C|T]) -->
[C],
{ code_type(C, alnum)
},
!,
name_codes(T).
name_codes([]) -->
[].
% escaped_uri_codes(-CodeList)
%
% Decode string holding %xx escaped characters.
escaped_uri_codes([]) -->
[].
escaped_uri_codes([C|T]) -->
"%", [D0,D1],
!,
{ code_type(D0, xdigit(V0)),
code_type(D1, xdigit(V1)),
C is V0<<4 + V1
},
escaped_uri_codes(T).
escaped_uri_codes([C|T]) -->
"\\u", [D0,D1,D2,D3],
!,
{ code_type(D0, xdigit(V0)),
code_type(D1, xdigit(V1)),
code_type(D2, xdigit(V2)),
code_type(D3, xdigit(V3)),
C is V0<<12 + V1<<8 + V2<<4 + V3
},
escaped_uri_codes(T).
escaped_uri_codes([C|T]) -->
"\\U", [D0,D1,D2,D3,D4,D5,D6,D7],
!,
{ code_type(D0, xdigit(V0)),
code_type(D1, xdigit(V1)),
code_type(D2, xdigit(V2)),
code_type(D3, xdigit(V3)),
code_type(D4, xdigit(V4)),
code_type(D5, xdigit(V5)),
code_type(D6, xdigit(V6)),
code_type(D7, xdigit(V7)),
C is V0<<28 + V1<<24 + V2<<20 + V3<<16 +
V4<<12 + V5<<8 + V6<<4 + V7
},
escaped_uri_codes(T).
escaped_uri_codes([C|T]) -->
[C],
escaped_uri_codes(T).
% lang_string()
%
% Process a language string
lang_string(String) -->
"\"",
string(Codes),
"\"",
!,
{ atom_codes(Atom, Codes)
},
( langsep
-> language(Lang),
{ String = literal(lang(Lang, Atom))
}
; "^^"
-> uniref(Type),
{ String = literal(type(Type, Atom))
}
; { String = literal(Atom)
}
).
langsep -->
"-".
langsep -->
"@".
% xml_string(String)
%
% Handle xml"..."
xml_string(xml(String)) -->
"xml\"", % really no whitespace?
string(Codes),
"\"",
{ atom_codes(String, Codes)
}.
string([]) -->
[].
string([C0|T]) -->
string_char(C0),
string(T).
string_char(0'\\) -->
"\\\\".
string_char(0'") -->
"\\\"".
string_char(10) -->
"\\n".
string_char(13) -->
"\\r".
string_char(9) -->
"\\t".
string_char(C) -->
"\\u",
'4xdigits'(C).
string_char(C) -->
"\\U",
'4xdigits'(C0),
'4xdigits'(C1),
{ C is C0<<16 + C1
}.
string_char(C) -->
[C].
'4xdigits'(C) -->
[C0,C1,C2,C3],
{ code_type(C0, xdigit(V0)),
code_type(C1, xdigit(V1)),
code_type(C2, xdigit(V2)),
code_type(C3, xdigit(V3)),
C is V0<<12 + V1<<8 + V2<<4 + V3
}.
% language(-Lang)
%
% Return xml:lang language identifier.
language(Lang) -->
lang_code(C0),
lang_codes(Codes),
{ atom_codes(Lang, [C0|Codes])
}.
lang_code(C) -->
[C],
{ C \== 0'.,
\+ code_type(C, white)
}.
lang_codes([C|T]) -->
lang_code(C),
!,
lang_codes(T).
lang_codes([]) -->
[].
%! add_prefix(+Request)
%
% Register a new prefix
add_prefix(Request) :-
http_parameters(Request,
[ prefix(Prefix),
uri(URI),
repository(Repository),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
authorized_api(write(Repository, add_prefix), ResultFormat),
check_prefix(Prefix),
api_action(Request,
rdf_register_prefix(Prefix, URI),
ResultFormat,
'Register prefix ~w --> ~w'-[Prefix, URI]).
del_prefix(Request) :-
http_parameters(Request,
[ prefix(Prefix),
repository(Repository),
resultFormat(ResultFormat)
],
[ attribute_declarations(attribute_decl)
]),
authorized_api(write(Repository, del_prefix), ResultFormat),
( rdf_current_prefix(Prefix, URI)
-> api_action(Request,
rdf_unregister_prefix(Prefix),
ResultFormat,
'Removed prefix ~w (was ~w)'-[Prefix, URI])
; api_action(Request,
true,
ResultFormat,
'Prefix ~w was unknown'-[Prefix])
).
:- if(\+current_predicate(rdf_unregister_prefix/1)).
rdf_unregister_prefix(Prefix) :-
retractall(rdf_db:ns(Prefix, _)).
:- endif.
check_prefix(Prefix) :-
xml_name(Prefix),
!.
check_prefix(Prefix) :-
domain_error(xml_name, Prefix).
/*******************************
* HTTP ATTRIBUTES *
*******************************/
%! attribute_decl(+OptionName, -Options)
%
% Default options for specified attribute names. See
% http_parameters/3.
attribute_decl(repository,
[ optional(true),
description('Name of the repository (ignored)')
]).
attribute_decl(query,
[ description('SPARQL or SeRQL quer-text')
]).
attribute_decl(queryLanguage,
[ default('SPARQL'),
oneof(['SeRQL', 'SPARQL']),
description('Query language used in query-text')
]).
attribute_decl(serialization,
[ default(rdfxml),
oneof([ rdfxml,
ntriples,
n3
]),
description('Serialization for graph-data')
]).
attribute_decl(resultFormat,
[ optional(true),
oneof([ xml,
html,
rdf,
json,
csv
]),
description('Serialization format of the result')
]).
attribute_decl(resourceFormat,
[ default(ns),
oneof([ plain,
ns,
nslabel
]),
description('How to format URIs in the table')
]).
attribute_decl(entailment, % cache?
[ default(Default),
oneof(Es),
description('Reasoning performed')
]) :-
setting(cliopatria:default_entailment, Default),
findall(E, cliopatria:entailment(E, _), Es).
attribute_decl(dataFormat,
[ optional(true),
oneof([rdfxml, ntriples, turtle]),
description('Serialization of the data')
]).
attribute_decl(baseURI,
[ default('http://example.org/'),
description('Base URI for relative resources')
]).
attribute_decl(source,
[ description('Name of the graph')
]).
attribute_decl(verifyData,
[ description('Verify the data (ignored)')
| Options
]) :-
bool(off, Options).
attribute_decl(schema,
[ description('Include schema RDF in downloaded graph')
| Options
]) :-
bool(off, Options).
attribute_decl(data,
[ description('Include non-schema RDF in downloaded graph')
| Options
]) :-
bool(off, Options).
attribute_decl(explicitOnly,
[ description('Do not include entailed triples')
| Options
]) :-
bool(off, Options).
attribute_decl(niceOutput,
[ description('Produce human-readable output (ignored; we always do that)')
| Options
]) :-
bool(off, Options).
attribute_decl(user,
[ description('User name')
]).
attribute_decl(password,
[ description('Clear-text password')
]).
% Our extensions
attribute_decl(storeAs,
[ default(''),
description('Store query under this name')
]).
attribute_decl(persistent,
[ description('Modify persistency of a graph'),
oneof([on, off])
]).
attribute_decl(uri,
[ description('URI')
]).
attribute_decl(prefix,
[ description('Prefix (abbreviation)')
]).
bool(Def,
[ default(Def),
oneof([on, off])
]).
%! result_format(+Request, ?Format) is det.
result_format(_Request, Format) :-
atom(Format),
!.
result_format(Request, _Format) :-
memberchk(accept(Accept), Request),
debug(sparql(result), 'Got accept = ~q', [Accept]),
fail.
result_format(_Request, xml).
accept_output_format(Request, Format) :-
memberchk(accept(Accept), Request),
( atom(Accept)
-> http_parse_header_value(accept, Accept, Media)
; Media = Accept
),
find_media(Media, Format),
!.
accept_output_format(_, xml).
find_media([media(Type, _, _, _)|T], Format) :-
( sparql_media(Type, Format)
-> true
; find_media(T, Format)
).
sparql_media(application/'sparql-results+xml', xml).
sparql_media(application/'sparql-results+json', json).
%! api_action(+Request, :Goal, +Format, +Message)
%
% Perform some -modifying- goal, reporting time, triples and
% subject statistics.
%
% @param Format specifies the result format and is one of =html=,
% =xml= or =rdf=.
% @param Message is passed to html_write//1.
api_action(Request, G, html, Message) :-
!,
call_showing_messages(
api_action2(Request, G, html, Message),
[ header(h4(Message)),
footer([])
]).
api_action(Request, G, Format, Message) :-
api_action2(Request, G, Format, Message).
api_action2(_Request, G, Format, Message) :-
logged_on(User, anonymous),
get_time(T0), T is integer(T0),
statistics(cputime, CPU0),
rdf_statistics(triples(Triples0)),
subjects(Subjects0),
run(G, sesame(User, T)),
subjects(Subjects1),
rdf_statistics(triples(Triples1)),
statistics(cputime, CPU1),
CPU is CPU1 - CPU0,
Triples is Triples1 - Triples0,
Subjects is Subjects1 - Subjects0,
done(Format, Message, CPU, Subjects, Triples).
:- if(rdf_statistics(subjects(_))). % RDF 2.x
subjects(Count) :- rdf_statistics(subjects(Count)).
subj_label --> html('Subjects').
:- else. % RDF 3.0
subjects(Count) :- rdf_statistics(resources(Count)).
subj_label --> html('Resources').
:- endif.
:- meta_predicate
run(0, +).
run(M:(A,B), Log) :-
!,
run(M:A, Log),
run(M:B, Log).
run(Goal, _) :-
no_transaction(Goal),
!,
call(Goal).
run(A, Log) :-
rdf_transaction(A, Log).
no_transaction(_:rdf_reset_db).
no_transaction(_:rdf_unload_graph(_)).
no_transaction(_:rdf_flush_journals(_)).
no_transaction(cpa_browse:multigraph_action(_,_)).
done(html, _Message, CPU, Subjects, Triples) :-
after_messages([ \result_table(CPU, Subjects, Triples)
]).
done(Format, _:Message, CPU, Subjects, Triples) :-
!,
done(Format, Message, CPU, Subjects, Triples).
done(json, Fmt-Args, _CPU, _Subjects, _Triples) :-
format(string(Message), Fmt, Args),
format('Content-type: application/json~n~n'),
json_write(current_output,
json([transaction=
json([status=
json([msg=Message])])])),
format('~n').
done(xml, Fmt-Args, _CPU, _Subjects, _Triples) :-
format(string(Message), Fmt, Args),
format('Content-type: text/xml~n~n'),
format('~n'),
format(' ~n'),
format(' ~w~n', [Message]),
format(' ~n'),
format('~n').
done(Format, Fmt-Args, _CPU, _Subjects, _Triples) :-
format('Content-type: text/plain~n~n'),
format('resultFormat=~w not yet supported~n~n', Format),
format(Fmt, Args).
%! result_table(+CPU, +SubDiff, +TripleDiff)// is det.
%
% HTML component that summarises the result of an operation.
result_table(CPU, Subjects, Triples) -->
{ rdf_statistics(triples(TriplesNow)),
subjects(SubjectsNow)
},
html([ h4('Operation completed'),
table([ id('result'),
class(block)
],
[ tr([td(class(empty), ''), th('+/-'), th('now')]),
tr([th(class(p_name), 'CPU time'),
\nc('~3f', CPU), td('')]),
tr([th(class(p_name), \subj_label),
\nc('~D', Subjects), \nc('~D', SubjectsNow)]),
tr([th(class(p_name), 'Triples'),
\nc('~D', Triples), \nc('~D', TriplesNow)])
])
]).
%! authorized_api(+Action, +ResultFormat) is det.
%
% @error permission_error(http_location, access, Path)
authorized_api(Action, ResultFormat) :-
ResultFormat == html, % do not bind
!,
authorized(Action).
authorized_api(Action, _) :-
logged_on(User, anonymous),
check_permission(User, Action).