dependencies
| (this space intentionally left almost blank) | ||||||||||||||||||||||||||||||||||||||||||||||||
0.1 | (defproject r2r-designer
:description "backend for the r2r-designer"
:url "http://rdanitz.github.io/r2r-designer/"
:license { :name "MIT"
:url "http://opensource.org/licenses/MIT" }
:repositories [["aksw-internal" "http://maven.aksw.org/repository/internal/"]]
:dependencies [
[org.clojure/clojure "1.6.0"]
[org.clojure/java.jdbc "0.3.3"]
[org.clojure/data.json "0.2.4"]
[com.stuartsierra/component "0.2.1"]
[compojure "1.1.8"]
[ring/ring-core "1.3.2"]
[ring-server "0.4.0"]
[ring-cors "0.1.4"]
[ring/ring-json "0.3.1"]
[clj-http "0.9.2"]
[edu.ucdenver.ccp/kr-sesame-core "1.4.17"]
[com.taoensso/timbre "3.2.1"]
[org.postgresql/postgresql "9.3-1102-jdbc41"]
[com.zaxxer/HikariCP-java6 "2.2.5"]
[org.aksw.sparqlify/sparqlify-core "0.6.12" :exclusions [[postgresql/postgresql]]]
[org.clojure/data.csv "0.1.2"]
]
:plugins [;; for standalone server:
;;'lein ring uberjar'
;;'java -jar target/r2r-designer-{version}-standalone.jar
[lein-ring "0.9.1"]
;; invoke with 'lein marg -f server.html .'
[lein-marginalia "0.8.0"]]
:ring {:init system/init
:destroy system/destroy
:handler system/app}
:source-paths ["server/src"]
:test-paths ["server/test"]
:resource-paths ["server/resources"]
:main system
:profiles {
:dev {
:dependencies [
[org.clojure/tools.namespace "0.2.5"]
[org.clojure/java.classpath "0.2.2"]
[ring-mock "0.1.5"]
[ring/ring-devel "1.3.2"]
]
:source-paths ["server/dev"]
:resource-paths [".tmp" "app"]
}
:uberjar {
:aot [system]
:main system
}
}) | ||||||||||||||||||||||||||||||||||||||||||||||||
Namespace 'user' will always be executed. Call '(dev)' to get things started. | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn dev [] (require 'dev) (in-ns 'dev)) | |||||||||||||||||||||||||||||||||||||||||||||||||
(do (clojure.core/ns system.main (:gen-class)) (clojure.core/defn -main [] ((do (clojure.core/require (quote ring.server.leiningen)) (clojure.core/resolve (quote ring.server.leiningen/serve))) (quote {:ring {:auto-reload? false, :stacktraces? false, :open-browser? false, :destroy system/destroy, :init system/init, :handler system/app}})))) | |||||||||||||||||||||||||||||||||||||||||||||||||
Every separate module within the backend is organized as a component; every component consists of a list of dependencies to other components, and a lifecycle to create, start and stop the component. | (ns components) | ||||||||||||||||||||||||||||||||||||||||||||||||
Encapsulates a database connection, as well as CSV file management. | (ns components.datasource
(:require
[taoensso.timbre :as timbre]
[com.stuartsierra.component :as c]
[core.db :refer [new-pool]])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord Datasource [spec pool csv-file]
c/Lifecycle
(start [c]
(info "starting datasource adapter ...")
(reset! (:pool c) (new-pool c))
c)
(stop [c]
(info "stopping datasource adapter ...")
(when (:pool c)
(if @(:pool c) (.close @(:pool c)))
(reset! (:pool c) nil))
(reset! (:csv-file c) nil)
(reset! (:separator c) nil)
c)) | |||||||||||||||||||||||||||||||||||||||||||||||||
Creates a new datasource component. Consists of: 'spec': holds the db params, 'pool': connection pool for the db, 'csv-file': current active file descriptor, 'separator': according csv file separator, 'max-pool': number of connections. | (defn new-datasource
[opts]
(map->Datasource {:spec (atom (select-keys opts [:driver :host :name :username :password]))
:pool (atom nil)
:csv-file (atom nil)
:separator (atom nil)
:max-pool 10})) | ||||||||||||||||||||||||||||||||||||||||||||||||
Logging component. | (ns components.logging
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord Logger [config]
c/Lifecycle
(start [component]
(info "start logging ...")
(timbre/merge-config! config)
component)
(stop [component]
(info "stop logging ...")
component)) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn new-logger [config]
(map->Logger {:config config})) | |||||||||||||||||||||||||||||||||||||||||||||||||
Encapsulates an OpenRDF connection. | (ns components.openrdf
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[core.openrdf :refer :all])
(:import
org.openrdf.repository.http.HTTPRepository)) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord OpenRDF []
c/Lifecycle
(start [c]
(info "starting openrdf ...")
(reset! (:server c) (HTTPRepository. (:host c) (:repo c)))
c)
(stop [c]
(info "stopping openrdf ...")
(reset! (:server c) nil)
c)) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn new-openrdf [opts]
(map->OpenRDF {:server (atom nil)
:host (:host opts)
:repo (:repo opts)
:base-uri (:base-uri opts)
})) | |||||||||||||||||||||||||||||||||||||||||||||||||
Encapsulates the Suggestion API (LOV or LinDA oracle). | (ns components.oracle
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[core.oracle :refer :all])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord Oracle [kb endpoint sample threshold limit n]
c/Lifecycle
(start [component]
(info "starting oracle ...")
(reset! (:kb component)
(new-server (:endpoint component)))
component)
(stop [component]
(info "stopping oracle ...")
(reset! (:kb component) nil)
component)) | |||||||||||||||||||||||||||||||||||||||||||||||||
Creates a new Oracle adapter with endpoint. | (defn new-oracle
[endpoint]
(map->Oracle {:endpoint endpoint
;; knowledge base
:kb (atom nil)
;; number of data points to regard
:sample 20
;; min score for suggestions
:threshold 0.4
;; number of suggestions
:limit 20
:n 5})) | ||||||||||||||||||||||||||||||||||||||||||||||||
Encapsulates the Ring web server holding the web app. | (ns components.ring
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[routes.app :refer [app-fn]]
[ring.server.standalone :refer [serve]])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord Ring [server app-fn opts db-api oracle-api]
c/Lifecycle
(start [component]
(info "starting ring adapter ...")
(let [app (app-fn component)]
(reset! server (serve app opts))
component))
(stop [component]
(info "stopping ring adapter ...")
(if @server
(.stop @server)
(reset! server nil))
component)) | |||||||||||||||||||||||||||||||||||||||||||||||||
Creates a new Ring adapter, holding the server and all api pathes. | (defn new-ring
[app-fn opts]
(map->Ring {:opts opts
:server (atom nil)
:app-fn app-fn
:db-api "/api/v1/db"
:csv-api "/api/v1/csv"
:oracle-api "/api/v1/oracle"
:transform-api "/api/v1/transform"
:file-store (atom {})})) | ||||||||||||||||||||||||||||||||||||||||||||||||
Encapsulates the Sparqlify SPARQL-SQL-Rewriter engine. | (ns components.sparqlify
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[core.sparqlify :refer :all])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defrecord Sparqlify [host port server]
c/Lifecycle
(start [c]
(info "starting sparqlify ...")
(when (:server c)
(if @(:server c) (.start @(:server c))))
c)
(stop [c]
(info "stopping sparqlify ...")
(when (:server c)
(if @(:server c) (.stop @(:server c)))
(reset! (:server c) nil))
c)) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn new-sparqlify [opts]
(map->Sparqlify {:host (:host opts)
:port (:port opts)
:server (atom nil)})) | |||||||||||||||||||||||||||||||||||||||||||||||||
The 'core' namespace carries the implementation of the main functionality. It also provides purely functional API wrappers to external libs and services. | (ns core) | ||||||||||||||||||||||||||||||||||||||||||||||||
Core CSV functionality. | (ns core.csv
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[clojure.java.io :as io]
[clojure.data.csv :as csv]
[clojure.string :as str])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
guesses (stupidly) the separator used in a csv file | (defn separator
[file]
(let [first-line (re-find #".*[\n\r]" (slurp file))
tabs (count (filter #(= \tab %) first-line))
commata (count (filter #(= \, %) first-line))]
(if (> tabs commata)
\tab
\,))) | ||||||||||||||||||||||||||||||||||||||||||||||||
sets a new CSV file and its separator | (defn set-file [c file] (reset! (:csv-file c) file) (reset! (:separator c) (separator file))) | ||||||||||||||||||||||||||||||||||||||||||||||||
retrieves the first 10 data items of the saved csv file | (defn get-data
[c]
(let [file @(:csv-file c)
separator @(:separator c)]
(if file
(with-open [r (io/reader file)] (doall (take 10 (csv/read-csv r :separator separator))))
[]))) | ||||||||||||||||||||||||||||||||||||||||||||||||
Core SQL database (PostgreSQL) functionality. | (ns core.db
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre]
[clojure.java.io :as io]
[clojure.java.jdbc :as jdbc]
[clojure.string :as str])
(:import
[java.sql DriverManager]
[com.zaxxer.hikari HikariDataSource HikariConfig])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
creates a new Hikari connection pool | (defn new-pool
[c]
(let [max-pool (:max-pool c)
spec @(:spec c)
ds (HikariDataSource.)]
(doto ds
(.setMaximumPoolSize max-pool)
(.setConnectionTestQuery "SELECT 1")
(.setDataSourceClassName (:driver spec))
(.addDataSourceProperty "serverName" (:host spec))
(.addDataSourceProperty "databaseName" (:name spec))
(.addDataSourceProperty "user" (:username spec))
(.addDataSourceProperty "password" (:password spec)))
ds)) | ||||||||||||||||||||||||||||||||||||||||||||||||
tries to connect to a given database via spec | (defn test-db
[spec]
(let [host (:host spec)
name (:name spec)
user (:username spec)
pass (:password spec)
connection-str (str "jdbc:postgresql://" host "/" name)]
(try
(let [conn (DriverManager/getConnection connection-str user pass)]
(.close conn)
true)
(catch Exception e false)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
setse a new database configuration as active | (defn register-db [db new-spec] (info "registering new data source") (if db (c/stop db)) (reset! (:spec db) new-spec) (c/start db)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns all db tables | (defn get-tables
[c]
(let [ds {:datasource @(:pool c)}
result (jdbc/query ds "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'public'")
tables (map :table_name result)]
(debug tables)
tables)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns all columns for table | (defn get-columns
[c table]
(let [ds {:datasource @(:pool c)}
result (jdbc/query ds (str "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '" table "'"))
columns (map :column_name result)]
(debug columns)
columns)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns a map with tables mapped to their columns | (defn get-table-columns
[c]
(let [ds {:datasource @(:pool c)}
tables (get-tables c)
table-columns (apply merge (for [table tables] {table (get-columns c table)}))]
table-columns)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns a column | (defn query-column
[c table column]
(let [ds {:datasource @(:pool c)}
result (jdbc/query ds (str "SELECT \"" column "\" FROM " table))
values (map second (map first result))]
(debug values)
values)) | ||||||||||||||||||||||||||||||||||||||||||||||||
core OpenRDF functions | (ns core.openrdf
(:require
[com.stuartsierra.component :as c]
[taoensso.timbre :as timbre])
(:import
org.openrdf.rio.RDFFormat
org.openrdf.model.Resource)) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
uploads a N3 triple dump to OpenRDF | (defn upload!
[c f]
(let [repo @(:server c)
uri (:base-uri c)]
(with-open [conn (.getConnection repo)]
(.add conn f uri RDFFormat/NTRIPLES (into-array Resource [])))
(str (:host c) "/" (:repo c)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
naïve implementation of the Oracle RDF suggestion engine | (ns core.oracle
(:require
[clojure.data.json :as json]
[clojure.string :refer [join split]]
[clj-http.client :as client]
[edu.ucdenver.ccp.kr.kb :refer :all]
[edu.ucdenver.ccp.kr.rdf :refer :all]
[edu.ucdenver.ccp.kr.sparql :refer :all]
[edu.ucdenver.ccp.kr.sesame.kb :as sesame]
[taoensso.timbre :as timbre]
[core.db :as db])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
returns a sesame server wrapping the given endpoint | (defn new-server
[endpoint]
(open
(sesame/new-sesame-server
:server endpoint
:repo-name "lov"))) | ||||||||||||||||||||||||||||||||||||||||||||||||
adds RDF namespaces to a knowledge base | (defn add-namespaces
[kb]
(update-namespaces kb
'(("rdf" "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
("rdfs" "http://www.w3.org/2000/01/rdf-schema#")
("dcterms" "http://purl.org/dc/terms/")
("skos" "http://www.w3.org/2004/02/skos/core#")))) | ||||||||||||||||||||||||||||||||||||||||||||||||
These are utility functions to retrieve several useful information about an RDF entity: | |||||||||||||||||||||||||||||||||||||||||||||||||
tries to find a label for the RDF entity | (defn get-label
[c entity]
(let [kb (add-namespaces @(:kb c))]
(let [result (sparql-query kb (format "select distinct * where { <%s> <http://www.w3.org/2000/01/rdf-schema#label> ?x. } limit 1" entity))]
(map '?/x result)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
tries to find a comment for the RDF entity | (defn get-comment
[c entity]
(let [kb (add-namespaces @(:kb c))]
(let [result (sparql-query kb (format "select distinct * where { <%s> <http://www.w3.org/2000/01/rdf-schema#comment> ?x. } limit 1" entity))]
(map '?/x result)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
tries to find the title for the RDF entity | (defn get-title
[c entity]
(let [kb (add-namespaces @(:kb c))]
(let [result (sparql-query kb (format "select distinct * where { <%s> <http://purl.org/dc/terms/title> ?x. } limit 1" entity))]
(map '?/x result)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
tries to find a description for the RDF entity | (defn get-description
[c entity]
(let [kb (add-namespaces @(:kb c))]
(let [result1 (sparql-query kb (format "select distinct * where { <%s> <http://purl.org/dc/terms/description> ?x. } limit 1" entity))
entity2 (if (and entity (.endsWith entity "#")) (subs entity 0 (dec (count entity))))
result2 (if entity2 (sparql-query kb (format "select distinct * where { <%s> <http://purl.org/dc/terms/description> ?x. } limit 1" entity2)) [])
merged (concat result1 result2)]
(map '?/x merged)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
tries to find a definition for the RDF entity | (defn get-definition
[c entity]
(let [kb (add-namespaces @(:kb c))]
(let [result (sparql-query kb (format "select distinct * where { <%s> <http://www.w3.org/2004/02/skos/core#definition> ?x. } limit 1" entity))]
(map '?/x result)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns all hits with score > .4 | (defn significant [c results] (filter #(> (:score %) (:threshold c)) results)) | ||||||||||||||||||||||||||||||||||||||||||||||||
takes the first n matches | (defn cut [c results] (take (:n c) results)) | ||||||||||||||||||||||||||||||||||||||||||||||||
filters unneccesary information from the matches | (defn shaped
[results]
(let [filter-fn #(select-keys % [:score :uri :prefixedName :vocabulary.prefix])
filtered (map filter-fn results)]
filtered)) | ||||||||||||||||||||||||||||||||||||||||||||||||
adds a localName and the vocabulary URI to the matches | (defn transform
[entity-map]
(let [prefixedName (first (:prefixedName entity-map))
vocabPrefix (first (:vocabulary.prefix entity-map))
localName (if (and prefixedName vocabPrefix) (subs prefixedName (inc (.length vocabPrefix))))
uri (first (:uri entity-map))
vocab (if (and uri localName) (subs uri 0 (- (.length uri) (.length localName))))]
(assoc entity-map
:localName (if localName [localName] [])
:vocabulary.uri (if vocab [vocab] [])))) | ||||||||||||||||||||||||||||||||||||||||||||||||
adds additional useful information like comments, descriptions, definition, etc. | (defn enrich
[c entity-map]
(let [uri (first (:uri entity-map))
vocab (first (:vocabulary.uri entity-map))]
(assoc entity-map
:label (get-label c uri)
:comment (get-comment c uri)
:definition (get-definition c uri)
:vocabulary.title (get-title c vocab)
:vocabulary.description (get-description c vocab)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
combines all specified transformations (inner and outer transformation) | (defn process
[c raw]
(let [;; transformation on the whole result set
outer (->> raw
(significant c)
(cut c)
shaped)
;; transformation on individual matches
inner (map (comp #(enrich c %) transform) outer)]
inner)) | ||||||||||||||||||||||||||||||||||||||||||||||||
queries the Linked Open Vocabulary search API; type is either 'class' or 'property' | (defn query-lov
[c type query]
(if (and c query (seq query))
(let [api "http://lov.okfn.org/dataset/lov/api/v2/search"
results (-> (client/get api {:query-params {:q query :type type}}) :body json/read-json :results)]
results)
[])) | ||||||||||||||||||||||||||||||||||||||||||||||||
splits an expr to several items | (defn expr->items [expr] (filter seq (split expr #"[\s,;]+"))) | ||||||||||||||||||||||||||||||||||||||||||||||||
searches for classes in LOV | (defn search-classes [c expr] (process c (apply concat (map #(query-lov c "class" %) (expr->items expr))))) | ||||||||||||||||||||||||||||||||||||||||||||||||
searches for properties in LOV | (defn search-properties [c expr] (process c (apply concat (map #(query-lov c "property" %) (expr->items expr))))) | ||||||||||||||||||||||||||||||||||||||||||||||||
preferrably returns a tag for a given table, if existing; otherwise returns the name of the table | (defn item-or-tag [item tag] (if (empty? tag) item tag)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns suggestions for a given table and column; database has to be configured | (defn recommend
[c table columns]
(let [t-rec {:name (:name table)
:recommend (search-classes c (item-or-tag (:name table) (:tag table)))}
c-rec (for [i columns]
{:name (:name i)
:recommend (search-properties c (item-or-tag (:name i) (:tag i)))})]
{:table t-rec :columns c-rec})) | ||||||||||||||||||||||||||||||||||||||||||||||||
This is a clean wrapper around the Sparqlify functions, that are needed. Essentially, these are (dumber) re-implementations of the specific Main methods. | (ns core.sparqlify
(:require
[com.stuartsierra.component :as c]
[clojure.java.shell :as sh]
[clojure.java.io :as io]
[taoensso.timbre :as timbre])
(:import
java.io.File
org.apache.commons.cli.GnuParser
org.apache.commons.cli.Options
org.apache.jena.riot.out.NTriplesWriter
org.aksw.jena_sparql_api.core.utils.QueryExecutionUtils
;; org.aksw.sparqlify.core.RdfViewSystemOld
org.aksw.sparqlify.util.SparqlifyUtils
org.aksw.sparqlify.web.SparqlifyCliHelper
org.aksw.sparqlify.web.SparqlFormatterUtils
org.aksw.sparqlify.csv.CsvParserConfig
org.aksw.sparqlify.csv.CsvMapperCliMain
org.aksw.sparqlify.csv.InputSupplierCSVReader )) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
writes the mapping as string to a temporary file | (defn mapping-to-file
[mapping]
(let [mapping-file (if mapping (File/createTempFile "mapping" ".sml"))]
(when mapping-file
(spit mapping-file mapping))
mapping-file)) | ||||||||||||||||||||||||||||||||||||||||||||||||
needed? (defn init-sparqlify! [] (RdfViewSystemOld/initSparqlifyFunctions)) | |||||||||||||||||||||||||||||||||||||||||||||||||
pretends to be called as from the command line; returns a CLI config object pseudo-parse pseudo-options into config [...] | (defn mapping-to-config
[mapping-file]
(let [options (doto (Options.) (.addOption "m" "mapping" true ""))
parser (GnuParser.)
cmd (.parse parser options (into-array String ["-m" mapping-file]))
config (SparqlifyCliHelper/parseSmlConfigs cmd nil)]
config)) | ||||||||||||||||||||||||||||||||||||||||||||||||
returns a configured Sparqlify engine sadly cannot easily kill sub-processes when invoking jar's through shell but ... common, seriously guys? rough rewrite of sparqlify-core's main; most of the config is not needed? some vars in main are never used | (defn config-sparqlify
[c mapping-file]
;; (init-sparqlify!)
(let [pool @(:pool (:datasource c))
config (mapping-to-config mapping-file)]
(SparqlifyUtils/createDefaultSparqlifyEngine pool config nil nil))) | ||||||||||||||||||||||||||||||||||||||||||||||||
exposes a Sparqlify endpoint for a given mapping | (defn start-sparql-endpoint!
[sparqlify mapping-file]
(let [port (:port sparqlify)
qef (config-sparqlify sparqlify mapping-file)
server (org.aksw.sparqlify.web.Main/createSparqlEndpoint qef port)]
(info "publishing new SPARQL endpoint")
(if sparqlify (c/stop sparqlify))
(reset! (:server sparqlify) server)
(c/start sparqlify)
(str (:host sparqlify) ":" (:port sparqlify) "/sparql"))) | ||||||||||||||||||||||||||||||||||||||||||||||||
dumps the Sparqlify endpoint regarding the given mapping; returns a temp file containing the dump as N3 triples. | (defn sparqlify-dump
[c mapping-file]
(let [qef (config-sparqlify c mapping-file)
it (QueryExecutionUtils/createIteratorDumpTriples qef)
f (File/createTempFile "dump" ".nt")]
(with-open [out (io/output-stream f)]
(NTriplesWriter/write out it))
f)) | ||||||||||||||||||||||||||||||||||||||||||||||||
wrapper to sparqlify-csv command line tool; returns a temp file containing the dump as N3 triples. | (defn sparqlify-csv
[c mapping-file]
(let [csv-file @(:csv-file (:datasource c))]
(with-open [in (io/input-stream mapping-file)]
(let [template-config (CsvMapperCliMain/readTemplateConfig in nil)
view (first (.getDefinitions template-config)) ; pick only(?) view
f (File/createTempFile "dump" ".nt")
csv-config (doto (CsvParserConfig.) (.setFieldSeparator @(:separator (:datasource c))))
csv-reader (InputSupplierCSVReader. csv-file csv-config)
results (CsvMapperCliMain/createResultSetFromCsv csv-reader true (int 100))
it (CsvMapperCliMain/createTripleIterator results view)]
(with-open [out (io/output-stream f)]
(SparqlFormatterUtils/writeText out it))
f)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
Tools for interactive development with the REPL. This file should not be included in a production build of the application. | (ns dev
(:require
;; external namespaces
[clojure.java.io :as io]
[clojure.java.shell :as sh]
[clojure.java.javadoc :refer (javadoc)]
[clojure.java.jdbc :as jdbc]
[clojure.pprint :refer (pprint)]
[clojure.reflect :refer (reflect)]
[clojure.repl :refer (apropos dir doc find-doc pst source)]
[clojure.string :as string]
[clojure.test :as test]
[clojure.tools.namespace.repl :refer (refresh refresh-all)]
[clojure.tools.reader.edn :as edn]
[clojure.set :refer :all]
[clojure.java.classpath :refer :all]
[clojure.data.json :as json]
[clojure.data.csv :as data-csv]
[com.stuartsierra.component :as c]
[ring.server.standalone :refer :all]
[ring.middleware.file-info :refer :all]
[ring.middleware.file :refer :all]
[clj-http.client :as client]
[taoensso.timbre :as timbre]
[edu.ucdenver.ccp.kr.kb :as kb]
[edu.ucdenver.ccp.kr.rdf :as rdf]
[edu.ucdenver.ccp.kr.sparql :as sparql]
[edu.ucdenver.ccp.kr.sesame.kb :as sesame]
;; internal namespaces
[components.datasource :refer :all]
[components.oracle :refer :all]
[components.sparqlify :refer :all]
[components.openrdf :refer :all]
[components.ring :refer :all]
[core.db :refer :all]
[core.csv :as csv]
[core.oracle :refer :all]
[core.sparqlify :refer :all]
[core.openrdf :refer :all]
[routes.app :refer [app-fn]]
[system :refer [new-system]])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
The #'system var holds the system during development in the REPL. | |||||||||||||||||||||||||||||||||||||||||||||||||
A Var containing an object representing the application under development. | (def system nil) | ||||||||||||||||||||||||||||||||||||||||||||||||
(def log-config {:ns-whitelist []
:ns-blacklist []}) | |||||||||||||||||||||||||||||||||||||||||||||||||
Creates and initializes the system under development in the Var #'system. | (defn init
[]
(let [db-opts {:driver "org.postgresql.ds.PGSimpleDataSource"
:host "localhost"
:name "mydb"
:username "postgres"
:password ""
}
ring-opts {:port 3000
:open-browser? false
:join true
:auto-reload? true}
oracle-sparql-endpoint "http://localhost:8080/openrdf-sesame"
sparqlify-opts {:host "http://localhost"
:port 7531}
openrdf-opts {:host "http://localhost:8080/openrdf-sesame"
:repo "r2r"
:base-uri "http://mycompany.com"}]
(alter-var-root #'system (constantly (new-system db-opts #'app-fn ring-opts oracle-sparql-endpoint log-config sparqlify-opts openrdf-opts))))) | ||||||||||||||||||||||||||||||||||||||||||||||||
Starts the system running, updates the Var #'system. | (defn start [] (alter-var-root #'system c/start)) | ||||||||||||||||||||||||||||||||||||||||||||||||
Stops the system if it is currently running, updates the Var #'system. | (defn stop
[]
(alter-var-root #'system
(fn [s] (when s (c/stop s))))
:stopped) | ||||||||||||||||||||||||||||||||||||||||||||||||
Initializes and starts the system running. | (defn go [] (init) (start) :ready) | ||||||||||||||||||||||||||||||||||||||||||||||||
Stops the system, reloads modified source files, and restarts it. | (defn reset [] (stop) (refresh :after 'dev/go)) | ||||||||||||||||||||||||||||||||||||||||||||||||
The 'routes' namespaces defines the backend API of the r2r-designer. You can find the specification within /server/resource/public/api-docs.raml. | (ns routes
(:require
[ring.util.response :refer [response]])) | ||||||||||||||||||||||||||||||||||||||||||||||||
Implements Cross-Origin-Resource-Sharing by adding a corresponding header. | (defn preflight
[request]
(assoc
(response "CORS enabled")
:headers {"Access-Control-Allow-Origin" "*"
"Access-Control-Allow-Methods" "PUT, DELETE, POST, GET, OPTIONS, XMODIFY"
"Access-Control-Max-Age" "2520"
"Access-Control-Allow-Credentials" "true"
"Access-Control-Request-Headers" "x-requested-with, content-type, origin, accept"
"Access-Control-Allow-Content-Type" "*"
"Access-Control-Allow-Headers" "x-requested-with, content-type, origin, accept"})) | ||||||||||||||||||||||||||||||||||||||||||||||||
This configures and holds the web app with all its routes. | (ns routes.app
(:require
[clojure.stacktrace :as st]
[taoensso.timbre :as timbre]
[ring.middleware.cors :refer :all]
[ring.middleware.params :refer :all]
[ring.middleware.multipart-params :refer :all]
[ring.middleware.json :refer :all]
[ring.middleware.file-info :refer :all]
[compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[routes.db :refer [db-routes-fn]]
[routes.csv :refer [csv-routes-fn]]
[routes.oracle :refer [oracle-routes-fn]]
[routes.transform :refer [transform-routes-fn]])) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
some default routes | |||||||||||||||||||||||||||||||||||||||||||||||||
(defroutes app-routes
(route/resources "/" {:root "public"})
(route/not-found "Not Found!")) | |||||||||||||||||||||||||||||||||||||||||||||||||
CORS headers | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn allow-origin [handler]
(fn [request]
(let [response (handler request)
headers (:headers response)]
(assoc response :headers (assoc headers "Access-Control-Allow-Origin" "*"))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn allow-content-type [handler]
(fn [request]
(let [response (handler request)
headers (:headers response)]
(assoc response :headers (assoc headers "Access-Control-Allow-Content-Type" "*"))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn wrap-dir-index [handler]
(fn [req]
(handler
(update-in req [:uri]
#(if (= "/" %) "/index.html" %))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
middleware to monitor incoming requests and outgoing responses | (defn monitor
[handler]
(fn [{:keys [request-method uri query-string] :as request}]
(let [response (handler request)]
(debug request)
(info request-method uri query-string)
(let [{:keys [status body]} response]
(info status body)
(debug response)
response)))) | ||||||||||||||||||||||||||||||||||||||||||||||||
catches every non-catched exceptions and wraps it into a 500 with a corresponding fault string | (defn wrap-exception
[f]
(fn [request]
(try (f request)
(catch Exception e
(let [stacktrace (with-out-str (st/print-stack-trace e))]
{:status 500
:body stacktrace}))))) | ||||||||||||||||||||||||||||||||||||||||||||||||
creates an app dynamically according to the current system | (defn app-fn
[component]
;; 'routes' bundles all routes which constitutes the API
(-> (routes (db-routes-fn component)
(csv-routes-fn component)
(oracle-routes-fn component)
(transform-routes-fn component)
app-routes)
wrap-params
wrap-multipart-params
(wrap-json-body {:keywords? true})
(wrap-json-response {:pretty true})
wrap-exception
allow-content-type
allow-origin
wrap-dir-index
wrap-file-info
monitor)) | ||||||||||||||||||||||||||||||||||||||||||||||||
(ns routes.csv
(:require
[compojure.core :refer :all]
[taoensso.timbre :as timbre]
[ring.util.codec :as codec]
[core.csv :refer :all]
[clojure.data.json :refer [write-str]]
[routes :refer [preflight]])) | |||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn csv-routes-fn [component]
(let [api (:csv-api component)
ds (:datasource component)]
(defroutes csv-routes
(GET (str api "/data") []
(try
(write-str (get-data ds))
(catch Exception _ {:status 500})))
(OPTIONS (str api "/upload") request (preflight request))
(POST (str api "/upload") request
(let [file (-> request :multipart-params (get "file") :tempfile)]
(try
(do
(set-file ds file)
{:status 200 :body "uploaded"})
(catch Exception _ {:status 400 :body "error"}))))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
(ns routes.db
(:require
[compojure.core :refer :all]
[taoensso.timbre :as timbre]
[ring.util.codec :as codec]
[core.db :refer :all]
[clojure.data.json :refer [write-str]]
[routes :refer [preflight]])) | |||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn db-routes-fn [component]
(let [api (:db-api component)
db (:datasource component)]
(defroutes db-routes
(GET (str api "/tables") []
(try
(write-str (get-tables db))
(catch Exception _ {:status 500})))
(GET (str api "/table-columns") []
(try
(write-str (get-table-columns db))
(catch Exception _ {:status 500})))
(GET (str api "/column") [table name :as r] (write-str (query-column db table name)))
(OPTIONS (str api "/test") request (preflight request))
(POST (str api "/test") [driver host name username password :as r]
(let [spec {:driver driver
:host host
:name name
:username username
:password password}
result (test-db spec)]
{:status 200 :body (str result)}))
; TODO: password is sent in plain text!
(OPTIONS (str api "/register") request (preflight request))
(POST (str api "/register") [driver host name username password :as r]
(let [db (:datasource component)
new-spec {:host host
:driver driver
:name name
:username username
:password password}]
(register-db db new-spec)
{:status 200}))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
(ns routes.oracle
(:require
[compojure.core :refer :all]
[taoensso.timbre :as timbre]
[ring.util.response :refer [response]]
[core.oracle :refer :all]
[routes :refer [preflight]])) | |||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn oracle-routes-fn [c]
(let [api (:oracle-api c)]
(defroutes oracle-routes
(OPTIONS api request (preflight request))
(POST api request
(let [oracle (:oracle c)
data (:body request)
suggestions (recommend oracle (:table data) (:columns data))]
(response suggestions)))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
(ns routes.transform
(:require
[compojure.core :refer :all]
[taoensso.timbre :as timbre]
[clojure.set :refer :all]
[ring.util.codec :as codec]
[ring.util.response :refer [response file-response]]
[clojure.java.io :as io]
[clojure.data.json :as json]
[core.db :as db]
[core.sparqlify :refer :all]
[core.openrdf :refer :all]
[routes :refer [preflight]])
(:import java.io.File)) | |||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
(defn transform-routes-fn [c]
(let [api (:transform-api c)]
(defroutes transform-routes
(OPTIONS (str api "/dump-db") r (preflight r))
(POST (str api "/dump-db") r
(let [sparqlify (:sparqlify c)
file-store (:file-store c)
mapping (spy (:mapping (:body r)))
f (spy (mapping-to-file mapping))
dump-file (spy (sparqlify-dump sparqlify (str f)))
-hash (spy (hash dump-file))]
(swap! file-store (fn [x] (assoc x -hash dump-file)))
{:status 200 :body (str (:transformApi c) "/file/" -hash ".nt")}))
(OPTIONS (str api "/dump-csv") r (preflight r))
(POST (str api "/dump-csv") r
(let [sparqlify (:sparqlify c)
file-store (:file-store c)
mapping (:mapping (:body r))
f (spy (mapping-to-file mapping))
dump-file (spy (sparqlify-csv sparqlify (str f)))
-hash (hash dump-file)]
(swap! file-store (fn [x] (assoc x -hash dump-file)))
{:status 200 :body (str (:transformApi c) "/file/" -hash ".nt")}))
(OPTIONS (str api "/publish/openrdf") r (preflight r))
(POST (str api "/publish/openrdf") r
(let [sparqlify (:sparqlify c)
openrdf (:openrdf c)
file-store (:file-store c)
mapping (:mapping (:body r))
f (mapping-to-file mapping)
dump-file (sparqlify-csv sparqlify (str f))
endpoint (upload! openrdf dump-file)]
{:status 200 :body endpoint}))
(OPTIONS (str api "/publish/sparqlify") r (preflight r))
(POST (str api "/publish/sparqlify") r
(let [sparqlify (:sparqlify c)
mapping (:mapping (:body r))
f (mapping-to-file mapping)
endpoint (start-sparql-endpoint! sparqlify (str f))]
{:status 200 :body endpoint}))
;; TODO: possible access to arbitrary files on the system through known filenames?
(GET (str api "/file/:id") [id]
(let [-hash (second (re-find #"(.*)\.nt" id))
sparqlify (:sparqlify c)
file-store (:file-store c)
f (get @file-store (Integer. -hash))]
(if f (file-response (str f)))))))) | |||||||||||||||||||||||||||||||||||||||||||||||||
This holds the system configuration at runtime. Every state is encapsuled in the #'system var. | (ns system
(:require
[taoensso.timbre :as timbre]
[com.stuartsierra.component :as c]
[components.datasource :refer :all]
[components.oracle :refer :all]
[components.sparqlify :refer :all]
[components.openrdf :refer :all]
[components.ring :refer :all]
[components.logging :refer :all]
[routes.app :refer [app-fn]])
(:gen-class)) | ||||||||||||||||||||||||||||||||||||||||||||||||
(timbre/refer-timbre) | |||||||||||||||||||||||||||||||||||||||||||||||||
Creates a new system configuration as pure data. | (defn new-system
[db-opts app-fn ring-opts oracle-sparql-endpoint log-config sparqlify-opts openrdf-opts]
(c/system-map
:log (c/using (new-logger log-config) [])
:datasource (c/using (new-datasource db-opts) [:log])
:oracle (c/using (new-oracle oracle-sparql-endpoint) [:datasource :log])
:sparqlify (c/using (new-sparqlify sparqlify-opts) [:datasource :log])
:openrdf (c/using (new-openrdf openrdf-opts) [:log])
:ring (c/using (new-ring app-fn ring-opts) [:datasource :oracle :sparqlify :openrdf :log]))) | ||||||||||||||||||||||||||||||||||||||||||||||||
configuration options | |||||||||||||||||||||||||||||||||||||||||||||||||
logging | (def log-config {:ns-whitelist []
:ns-blacklist []}) | ||||||||||||||||||||||||||||||||||||||||||||||||
initial database configuration | (def db-opts {:driver
:host
:name
:username
:password }) | ||||||||||||||||||||||||||||||||||||||||||||||||
ring web server options | (def ring-opts {:port 3000
:open-browser? false
:join true
:auto-reload? true}) | ||||||||||||||||||||||||||||||||||||||||||||||||
SPARQL endpoint for additional information from the oracle. As of now, the Linked Open Vocabulary SPARQL endpoint has been discontinued; use /resources/lov.n3 to populate a private OpenRDF repository or use another one. | (def oracle-sparql-endpoint "http://localhost:8080/openrdf-sesame") | ||||||||||||||||||||||||||||||||||||||||||||||||
Where to open up the SPARQL endpoint? | (def sparqlify-opts {:host "http://localhost"
:port 7531}) | ||||||||||||||||||||||||||||||||||||||||||||||||
Where to dump to the created data? | (def openrdf-opts {:host "http://localhost:8080/openrdf-sesame"
:repo "r2r"
:base-uri "http://mycompany.com"}) | ||||||||||||||||||||||||||||||||||||||||||||||||
Configure new system and start it. When running as a jar, the init fn has to be called by hand. | (defn -main []
(info "calling system/-main")
(let [system (new-system
db-opts
#'app-fn
ring-opts
oracle-sparql-endpoint
log-config
sparqlify-opts
openrdf-opts)]
(c/start system))) | ||||||||||||||||||||||||||||||||||||||||||||||||