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