├── .lein-classpath ├── resources ├── META-INF │ └── services │ │ └── java.sql.Driver └── public │ └── generated-doc │ ├── highlight │ ├── solarized-light.css │ └── highlight.min.js │ ├── js │ └── page_effects.js │ ├── buttle.util.html │ ├── buttle.driver-manager.html │ ├── buttle.event.html │ ├── buttle.data-source.html │ ├── buttle.connection-pool-data-source.html │ ├── buttle.xa-data-source.html │ ├── index.html │ ├── css │ └── default.css │ ├── buttle.proxy.html │ └── buttle.driver.html ├── .gitignore ├── java ├── buttle │ └── SetContextClassLoaderInStaticInitializer.java └── ButtleTest.java ├── examples └── buttle │ └── examples │ ├── java_events.clj │ ├── event_channel.clj │ ├── handle.clj │ └── open_tracing.clj ├── test └── buttle │ ├── xa_data_source_test.clj │ ├── connection_pool_data_source_test.clj │ ├── driver_manager_test.clj │ ├── data_source_test.clj │ ├── driver_test.clj │ ├── event_test.clj │ └── proxy_test.clj ├── src └── buttle │ ├── driver_manager.clj │ ├── event.clj │ ├── util.clj │ ├── data_source.clj │ ├── connection_pool_data_source.clj │ ├── xa_data_source.clj │ ├── proxy.clj │ └── driver.clj ├── plugin └── leiningen │ └── deploy_driver.clj └── project.clj /.lein-classpath: -------------------------------------------------------------------------------- 1 | plugin -------------------------------------------------------------------------------- /resources/META-INF/services/java.sql.Driver: -------------------------------------------------------------------------------- 1 | buttle.jdbc.Driver -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *~ 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /java/buttle/SetContextClassLoaderInStaticInitializer.java: -------------------------------------------------------------------------------- 1 | package buttle; 2 | 3 | public class SetContextClassLoaderInStaticInitializer { 4 | 5 | // https://docs.jboss.org/author/display/AS71/Class+Loading+in+AS7 6 | 7 | // https://docs.jboss.org/author/display/MODULES/Home 8 | 9 | static { 10 | Thread.currentThread().setContextClassLoader(SetContextClassLoaderInStaticInitializer.class.getClassLoader()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /examples/buttle/examples/java_events.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.examples.java-events 2 | (:require [clojure.core.async :as a] 3 | [buttle.event :as event])) 4 | 5 | ;; When this file/namespace is loaded it will start a go-loop which 6 | ;; consumes events from `buttle.event/event-mult` and calls 7 | ;; `ButtleTest/processEvent` (see `java/ButtleTest.java`). 8 | 9 | (let [ch (a/chan)] 10 | (a/tap event/event-mult ch) 11 | (a/go 12 | (loop [] 13 | (when-let [e (a/java-bean org.postgresql.xa.PGXADataSource 24 | {:url driver-test/postgres-url})) 25 | (let [buttle-xa-ds 26 | (doto (buttle.jdbc.XADataSource.) 27 | (.setDelegateSpec "\"foo-ds\""))] 28 | (with-open [conn (.getXAConnection 29 | buttle-xa-ds 30 | driver-test/buttle-user 31 | driver-test/buttle-password)])))) 32 | -------------------------------------------------------------------------------- /test/buttle/connection_pool_data_source_test.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.connection-pool-data-source-test 2 | (:require [clojure.test :refer :all] 3 | [buttle.driver-test :as driver-test] 4 | [buttle.util :as util] 5 | [buttle.data-source-test] ;; see buttle.data-source-test/def-once-set-initial-context-factory-builder 6 | [buttle.connection-pool-data-source :as cp])) 7 | 8 | (deftest create-instance 9 | (let [buttle-cp-ds 10 | (doto (buttle.jdbc.ConnectionPoolDataSource.) 11 | (.setDelegateSpec 12 | (format "{:delegate-class org.postgresql.ds.PGConnectionPoolDataSource 13 | :url %s}" 14 | (pr-str driver-test/postgres-url))))] 15 | (with-open [conn (.getPooledConnection 16 | buttle-cp-ds 17 | driver-test/buttle-user 18 | driver-test/buttle-password)]))) 19 | 20 | (deftest lookup-jndi 21 | (with-open [ctx (javax.naming.InitialContext.)] 22 | (.rebind ctx "foo-ds" 23 | (util/->java-bean org.postgresql.ds.PGConnectionPoolDataSource 24 | {:url driver-test/postgres-url})) 25 | (let [buttle-cp-ds 26 | (doto (buttle.jdbc.ConnectionPoolDataSource.) 27 | (.setDelegateSpec "\"foo-ds\""))] 28 | (with-open [conn (.getPooledConnection 29 | buttle-cp-ds 30 | driver-test/buttle-user 31 | driver-test/buttle-password)])))) 32 | -------------------------------------------------------------------------------- /test/buttle/driver_manager_test.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.driver-manager-test 2 | (:require [clojure.test :refer :all] 3 | [buttle.driver-manager :as mgr])) 4 | 5 | (mgr/register-driver (buttle.jdbc.Driver.)) 6 | 7 | (def postgres-url "jdbc:postgresql://127.0.0.1:6632/postgres") 8 | (def buttle-url (format "jdbc:buttle:{:user %s :password %s :target-url %s}" 9 | (pr-str (System/getenv "buttle_user")) 10 | (pr-str (System/getenv "buttle_password")) 11 | (pr-str postgres-url))) 12 | 13 | (defn get-postgres-connection [] 14 | (mgr/get-connection postgres-url 15 | (System/getenv "buttle_user") 16 | (System/getenv "buttle_password"))) 17 | 18 | (defn get-buttle-connection [] 19 | (mgr/get-connection buttle-url 20 | (System/getenv "buttle_user") 21 | (System/getenv "buttle_password"))) 22 | 23 | (deftest postgres-tests 24 | (testing "Just connecting to Postgres" 25 | (is (with-open [conn (get-postgres-connection)] 26 | "ok"))) 27 | (testing "Access pg_catalog.pg_tables" 28 | (is (with-open [conn (get-postgres-connection)] 29 | (-> conn 30 | .createStatement 31 | (.executeQuery "select * from pg_catalog.pg_tables where schemaname = 'pg_catalog'") 32 | (resultset-seq)))))) 33 | 34 | (deftest buttle-tests 35 | (testing "Just connecting to Postgres through buttle" 36 | (is (with-open [conn (get-buttle-connection)] 37 | "ok")))) 38 | 39 | -------------------------------------------------------------------------------- /resources/public/generated-doc/highlight/solarized-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #fdf6e3; 12 | color: #657b83; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #93a1a1; 18 | } 19 | 20 | /* Solarized Green */ 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-addition { 24 | color: #859900; 25 | } 26 | 27 | /* Solarized Cyan */ 28 | .hljs-number, 29 | .hljs-string, 30 | .hljs-meta .hljs-meta-string, 31 | .hljs-literal, 32 | .hljs-doctag, 33 | .hljs-regexp { 34 | color: #2aa198; 35 | } 36 | 37 | /* Solarized Blue */ 38 | .hljs-title, 39 | .hljs-section, 40 | .hljs-name, 41 | .hljs-selector-id, 42 | .hljs-selector-class { 43 | color: #268bd2; 44 | } 45 | 46 | /* Solarized Yellow */ 47 | .hljs-attribute, 48 | .hljs-attr, 49 | .hljs-variable, 50 | .hljs-template-variable, 51 | .hljs-class .hljs-title, 52 | .hljs-type { 53 | color: #b58900; 54 | } 55 | 56 | /* Solarized Orange */ 57 | .hljs-symbol, 58 | .hljs-bullet, 59 | .hljs-subst, 60 | .hljs-meta, 61 | .hljs-meta .hljs-keyword, 62 | .hljs-selector-attr, 63 | .hljs-selector-pseudo, 64 | .hljs-link { 65 | color: #cb4b16; 66 | } 67 | 68 | /* Solarized Red */ 69 | .hljs-built_in, 70 | .hljs-deletion { 71 | color: #dc322f; 72 | } 73 | 74 | .hljs-formula { 75 | background: #eee8d5; 76 | } 77 | 78 | .hljs-emphasis { 79 | font-style: italic; 80 | } 81 | 82 | .hljs-strong { 83 | font-weight: bold; 84 | } 85 | -------------------------------------------------------------------------------- /src/buttle/driver_manager.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.driver-manager 2 | "A thin/simple Clojure API around `java.sql.DriverManager`." 3 | 4 | (:import [java.sql DriverManager])) 5 | 6 | (defn register-driver 7 | "Registers the `driver` (see 8 | `java.sql.DriverManager.registerDriver(Driver)`)." 9 | 10 | [driver] 11 | (DriverManager/registerDriver driver)) 12 | 13 | (defn get-driver 14 | "Returns the registered `java.sql.Driver` which accepts the 15 | `url`. Throws if a driver cannot be found (see 16 | `java.sql.DriverManager.getDriver(String)`)." 17 | 18 | [url] 19 | (DriverManager/getDriver url)) 20 | 21 | (defn get-drivers 22 | "Returns a seq of all registered drivers (see 23 | `java.sql.DriverManager.getDrivers()`)" 24 | 25 | [] 26 | (enumeration-seq (DriverManager/getDrivers))) 27 | 28 | (defn get-connection 29 | "Finds the driver for `url` and uses it to open a 30 | `java.sql.Connection`. Returns the connection or throws if anything 31 | goes wrong (see `java.sql.DriverManager.getConnection(String, 32 | String, String)`)." 33 | 34 | ([url user password] 35 | (DriverManager/getConnection url user password)) 36 | ([url info] 37 | (DriverManager/getConnection url info))) 38 | 39 | (defn deregister-drivers 40 | "Iterates over all registered drivers (as of `(get-drivers)`) and 41 | deregisters each. Returns seq of drivers." 42 | 43 | [] 44 | (seq 45 | (doall 46 | (for [d (get-drivers)] 47 | (do 48 | (DriverManager/deregisterDriver d) 49 | d))))) 50 | 51 | -------------------------------------------------------------------------------- /src/buttle/event.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.event 2 | "Send events to consumers via `clojure.core.async/chan`. 3 | 4 | Consumers can `tap` on `event-mult` to receive events that are 5 | produced through `send-event`. `buttle.proxy/handle-default` is such 6 | a producer. 7 | 8 | Note that `send-event` __synchronuosly__ puts events onto channel 9 | `event-ch` which is the input channel for `event-mult`. When there 10 | is __no__ __channel__ connected to `event-mult` (which is the case 11 | when this namespace is loaded the first time) calling `send-event` 12 | will __not__ __block__ (`event-mult` will just eat up those 13 | events). When there __is__ one or more channels connected to 14 | `event-mult` (by consumers having called `clojure.core.async/tap`) 15 | calling `send-event` __will__ __block__ until the event has been 16 | sent/consumed by each of the connected channels. So make sure you 17 | have a `go` block consuming any channel that you connect to 18 | `event-mult`." 19 | 20 | (:require [clojure.core.async :as a])) 21 | 22 | (def event-ch 23 | "For internal use only. The channel through which events are 24 | sent. Is the input for `event-mult`." 25 | 26 | (a/chan)) 27 | 28 | (defn put-event 29 | "For internal use only. Sends event `e` to `event-ch`." 30 | 31 | [e] 32 | (a/>!! event-ch e)) 33 | 34 | (defn send-event 35 | "__Synchronuosly__ (__blocking__) sends event `e` to `event-ch` (via `put-event`). 36 | 37 | API for producing/sending events, which are then consumed through 38 | `event-mult` and conncted consumer channels (if present)." 39 | 40 | [e] 41 | (put-event e)) 42 | 43 | (def event-mult 44 | "API for consumers which want to receive events. Use 45 | `clojure.core.async/tap` to register your consumer 46 | `clojure.core.async/chan`" 47 | 48 | (a/mult event-ch)) 49 | 50 | -------------------------------------------------------------------------------- /java/ButtleTest.java: -------------------------------------------------------------------------------- 1 | import java.sql.Connection; 2 | import java.sql.DriverManager; 3 | import java.sql.ResultSet; 4 | import java.sql.Statement; 5 | 6 | public class ButtleTest { 7 | 8 | public static void processEvent(Object e) { 9 | System.out.println("event : " + e); 10 | } 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | // System.setProperty("buttle.user-form", "(load-file \"examples/buttle/examples/event_channel.clj\")"); 15 | // System.setProperty("buttle.user-form", "(load-file \"examples/buttle/examples/java_events.clj\")"); 16 | // System.setProperty("buttle.user-form", "(load-file \"examples/buttle/examples/handle.clj\")"); 17 | 18 | // needs -Dbuttle_jaeger_agent_host= 19 | System.setProperty("buttle.user-form", "(load-file \"examples/buttle/examples/open_tracing.clj\")"); 20 | 21 | // -Dbuttle_user= -Dbuttle_password= 22 | String user = System.getProperty("buttle_user"); 23 | String password = System.getProperty("buttle_password"); 24 | 25 | String jdbcUrl = "jdbc:postgresql://127.0.0.1:6632/postgres"; 26 | String buttleUrl = String.format("jdbc:buttle:{:user \"%s\" :password \"%s\" :target-url \"%s\"}", user, 27 | password, jdbcUrl); 28 | 29 | Connection conn = DriverManager.getConnection(buttleUrl, user, password); 30 | 31 | Statement stmt = conn.createStatement(); 32 | ResultSet rs = stmt.executeQuery("select * from pg_catalog.pg_tables where schemaname = 'pg_catalog'"); 33 | 34 | for (int cc = rs.getMetaData().getColumnCount(); rs.next();) { 35 | for (int i = 1; i <= cc; i++) { 36 | System.out.print(i == 1 ? "" : ","); 37 | // Watch out - output will be mixed up with output from Clojure 38 | // buttle.examples.event-channel/handle-with-log 39 | System.out.print(rs.getObject(i)); 40 | } 41 | System.out.println(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /examples/buttle/examples/handle.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.examples.handle 2 | (:require [buttle.proxy :as proxy])) 3 | 4 | ;; When this file/namespace is loaded it will (re-) register the 5 | ;; `:default` method implementation for `buttle.proxy/handle`. It just 6 | ;; prints log messages to stdout when entering and leaving methods 7 | ;; (and when the method call throws). This acts like an around advice 8 | ;; in AOP. 9 | ;; 10 | ;; You can use this in cases when you're using the _Buttle_ JDBC 11 | ;; driver but you don't have control over the main program flow. You 12 | ;; just need to define the system property `buttle.user-file` (see 13 | ;; `buttle.driver/eval-buttle-user-file!`) like so: 14 | ;; 15 | ;; set BUTTLE="-Dbuttle.user-file=C:/buttle-workspace/examples/buttle/examples/handle.clj" 16 | ;; java %BUTTLE% [...] 17 | 18 | (defn invoke-with-logging [the-method target-obj the-args] 19 | (println (format "buttle.examples.handle: INVOKE %s" 20 | (pr-str [the-method target-obj (into [] the-args)]))) 21 | (let [r (try 22 | (proxy/handle-default the-method target-obj the-args) 23 | (catch Throwable t 24 | (do 25 | (println (format "buttle.examples.handle: THROW %s : %s" 26 | (pr-str [the-method target-obj (into [] the-args)]) (pr-str t))) 27 | (throw t))))] 28 | (println (format "buttle.examples.handle: RETURN %s --> %s" 29 | (pr-str [the-method target-obj (into [] the-args)]) (pr-str r))) 30 | r)) 31 | 32 | ;; #_ ;; handle :default will print lots(!) of messages. 33 | (defmethod proxy/handle :default [the-method target-obj the-args] 34 | (invoke-with-logging the-method target-obj the-args)) 35 | 36 | #_ ;; just using [java.sql.Driver :buttle/default] prints only a few lines 37 | (proxy/def-handle [java.sql.Driver :buttle/default] [the-method target-obj the-args] 38 | (invoke-with-logging the-method target-obj the-args)) 39 | 40 | #_ ;; using [java.sql.Connection :buttle/default] prints a few more lines 41 | (proxy/def-handle [java.sql.Connection :buttle/default] [the-method target-obj the-args] 42 | (invoke-with-logging the-method target-obj the-args)) 43 | 44 | -------------------------------------------------------------------------------- /plugin/leiningen/deploy_driver.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.deploy-driver 2 | (:require [cemerick.pomegranate.aether :as aether] 3 | [leiningen.deploy :as ldeploy] 4 | [leiningen.core.main :as main])) 5 | 6 | ;; Original code copied from 7 | ;; https://github.com/technomancy/leiningen/blob/2.8.1/src/leiningen/deploy.clj 8 | ;; 9 | ;; The original calculates the :classifier from the given filename and 10 | ;; the version. See 11 | ;; 12 | ;; https://github.com/technomancy/leiningen/blob/2.8.1/src/leiningen/deploy.clj#L228 13 | ;; 14 | ;; For Buttle we want to build `target/uberjar/buttle-driver.jar` 15 | ;; which does not carry the version number. So here you pass in the 16 | ;; classifier. As an add-on this function supports pseudo repository 17 | ;; :leiningen/repository ("snapshots" for SNAPSHOT version, else 18 | ;; "releases") and pseudo versions :leiningen/version (means: take 19 | ;; version from project). This makes it easy to invoke this task 20 | ;; through :release-tasks. 21 | 22 | (defn deploy-driver 23 | "Deploys the Buttle driver (UBERJAR) to the given repo. 24 | 25 | Deploys the JAR **only**. The `pom.xml` will not be deployed. Use 26 | this to deploy just one file to a repository when you need a 27 | `classifier`. 28 | 29 | Example: build UBERJAR and deploy (works for snapshots and releases) 30 | 31 | lein uberjar 32 | lein deploy-driver :leiningen/repository buttle/buttle :leiningen/version driver target/uberjar/buttle-driver.jar 33 | 34 | " 35 | 36 | [project repository identifier version classifier file] 37 | (let [identifier (symbol identifier) 38 | artifact-id (name identifier) 39 | group-id (namespace identifier) 40 | version (if (= version ":leiningen/version") 41 | (:version project) 42 | version) 43 | repository (if (= repository ":leiningen/repository") 44 | (if (.endsWith version "-SNAPSHOT") 45 | "snapshots" 46 | "releases") 47 | repository) 48 | repo (ldeploy/repo-for project repository) 49 | artifact-map {[:extension "jar" 50 | :classifier classifier] file} 51 | deploy-args [:coordinates [(symbol group-id artifact-id) version] 52 | :artifact-map artifact-map 53 | :transfer-listener :stdout 54 | :repository [repo] 55 | :local-repo (:local-repo project)]] 56 | (System/setProperty "aether.checksums.forSignature" "true") 57 | ;; WARNING: prints passwords in repo map 58 | (main/info "deploy-driver: Deploying " deploy-args) 59 | (apply aether/deploy deploy-args))) 60 | 61 | -------------------------------------------------------------------------------- /src/buttle/util.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.util 2 | "Just some helpers." 3 | (:import [javax.naming InitialContext])) 4 | 5 | (defn log [& xs] 6 | "Log output." 7 | 8 | (let [s (apply pr-str xs)] 9 | (.println System/out (str "buttle: " s)) 10 | s)) 11 | 12 | (defmacro with-tccl 13 | "Thread-locally binds `clojure.core/*use-context-classloader*` to 14 | `true`, sets the current thread's context classloader to `cl` and 15 | executes `body` within that context. On completion restores the 16 | context classloader and pops 17 | `clojure.core/*use-context-classloader*` to the previous 18 | value. Returns value of `body` evaluation." 19 | 20 | [cl & body] 21 | `(binding [*use-context-classloader* true] 22 | (let [tccl# (.getContextClassLoader (Thread/currentThread))] 23 | (try (.setContextClassLoader (Thread/currentThread) ~cl) 24 | ~@body 25 | (finally 26 | (.setContextClassLoader (Thread/currentThread) tccl#)))))) 27 | 28 | (defn jndi-lookup 29 | "Looks up entry in JNDI and returns it. Throws if entry cannot be 30 | found." 31 | 32 | [jndi] 33 | (when-not jndi 34 | (throw (RuntimeException. "No `jndi` property set."))) 35 | (try 36 | (with-open [ctx (InitialContext.)] 37 | (.lookup ctx jndi)) 38 | (catch Throwable t 39 | (throw 40 | (RuntimeException. 41 | (format "JNDI lookup failed for '%s': %s" jndi t) t))))) 42 | 43 | (defn ->java-bean 44 | "Creates and returns instance of class-typed `class-spec` and sets 45 | Java-Bean properties via setters from `props` map. Setter-method 46 | names are derived from map keys by camel-casing `foo-bar` to 47 | `setFooBar`. Map values have to be type-conforming (no conversion is 48 | done)." 49 | 50 | [class-spec props] 51 | (try 52 | (let [new-instance (.newInstance class-spec) 53 | meths (->> class-spec 54 | .getMethods 55 | (map #(-> [(.getName %1) %1])) 56 | (into {}))] 57 | (doseq [[k v] props 58 | :let [mn (clojure.string/replace 59 | (str "set-" (name k)) 60 | #"-(.)" #(-> % second .toUpperCase)) 61 | m (meths mn)]] 62 | (when-not m 63 | (throw (RuntimeException. (format "Setter not found for %s. Known methods are %s" 64 | [k v mn] (keys meths))))) 65 | (try 66 | (.invoke m new-instance (into-array [v])) 67 | (catch Throwable t 68 | (throw 69 | (RuntimeException. 70 | (format "Calling %s/%s with %s failed: %s" 71 | m new-instance (pr-str v) t) 72 | t))))) 73 | new-instance) 74 | (catch Throwable t 75 | (throw 76 | (RuntimeException. 77 | (format "(->java-bean %s %s) fails: %s" 78 | class-spec props t) 79 | t))))) 80 | -------------------------------------------------------------------------------- /test/buttle/data_source_test.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.data-source-test 2 | (:require [clojure.test :refer :all] 3 | [buttle.driver-test :as driver-test] 4 | [buttle.util :as util] 5 | [buttle.data-source :as ds])) 6 | 7 | ;; https://docs.oracle.com/javase/8/docs/api/javax/naming/InitialContext.html 8 | ;; https://www.javacodegeeks.com/2012/04/jndi-and-jpa-without-j2ee-container.html 9 | 10 | (defn create-initial-context-factory [] 11 | (proxy [org.osjava.sj.MemoryContextFactory] [] 12 | (getInitialContext [env] 13 | (proxy-super 14 | getInitialContext 15 | (doto (.clone env) 16 | (.put "org.osjava.sj.jndi.ignoreClose" "true") 17 | (.put "org.osjava.sj.jndi.shared" "true")))))) 18 | 19 | (defonce def-once-set-initial-context-factory-builder 20 | (javax.naming.spi.NamingManager/setInitialContextFactoryBuilder 21 | (proxy [javax.naming.spi.InitialContextFactoryBuilder] [] 22 | (createInitialContextFactory [_] 23 | (create-initial-context-factory))))) 24 | 25 | (deftest sane-check 26 | (with-open [ctx (javax.naming.InitialContext.)] 27 | (.rebind ctx "foo" "bar")) 28 | (with-open [ctx (javax.naming.InitialContext.)] 29 | (is (= "bar" (.lookup ctx "foo"))))) 30 | 31 | (deftest create-instance 32 | (let [buttle-ds 33 | (doto (buttle.jdbc.DataSource.) 34 | (.setDelegateSpec 35 | (format "{:delegate-class org.postgresql.ds.PGSimpleDataSource 36 | :url %s}" 37 | (pr-str driver-test/postgres-url))))] 38 | (with-open [conn (.getConnection 39 | buttle-ds 40 | driver-test/buttle-user 41 | driver-test/buttle-password)]))) 42 | 43 | (deftest lookup-jndi 44 | (with-open [ctx (javax.naming.InitialContext.)] 45 | (.rebind ctx "foo-ds" 46 | (util/->java-bean org.postgresql.ds.PGSimpleDataSource 47 | {:url driver-test/postgres-url 48 | :user driver-test/buttle-user 49 | :password driver-test/buttle-password})) 50 | (let [buttle-ds 51 | (doto (buttle.jdbc.DataSource.) 52 | (.setDelegateSpec "\"foo-ds\""))] 53 | (with-open [conn (.getConnection 54 | buttle-ds 55 | driver-test/buttle-user 56 | driver-test/buttle-password)])))) 57 | 58 | (deftest lookup-connection 59 | (is (= "foo-connection" 60 | (let [_ (with-open [ctx (javax.naming.InitialContext.)] 61 | (.rebind ctx "foo-ds" 62 | (proxy [javax.sql.DataSource] [] 63 | (getConnection [& xs] 64 | (proxy [java.sql.Connection] [] 65 | (toString [] "foo-connection")))))) 66 | buttle-ds (doto (buttle.jdbc.DataSource.) 67 | (.setDelegateSpec "\"foo-ds\"")) 68 | conn (.getConnection buttle-ds)] 69 | (str conn))))) 70 | -------------------------------------------------------------------------------- /resources/public/generated-doc/js/page_effects.js: -------------------------------------------------------------------------------- 1 | function visibleInParent(element) { 2 | var position = $(element).position().top 3 | return position > -50 && position < ($(element).offsetParent().height() - 50) 4 | } 5 | 6 | function hasFragment(link, fragment) { 7 | return $(link).attr("href").indexOf("#" + fragment) != -1 8 | } 9 | 10 | function findLinkByFragment(elements, fragment) { 11 | return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first() 12 | } 13 | 14 | function scrollToCurrentVarLink(elements) { 15 | var elements = $(elements); 16 | var parent = elements.offsetParent(); 17 | 18 | if (elements.length == 0) return; 19 | 20 | var top = elements.first().position().top; 21 | var bottom = elements.last().position().top + elements.last().height(); 22 | 23 | if (top >= 0 && bottom <= parent.height()) return; 24 | 25 | if (top < 0) { 26 | parent.scrollTop(parent.scrollTop() + top); 27 | } 28 | else if (bottom > parent.height()) { 29 | parent.scrollTop(parent.scrollTop() + bottom - parent.height()); 30 | } 31 | } 32 | 33 | function setCurrentVarLink() { 34 | $('.secondary a').parent().removeClass('current') 35 | $('.anchor'). 36 | filter(function(index) { return visibleInParent(this) }). 37 | each(function(index, element) { 38 | findLinkByFragment(".secondary a", element.id). 39 | parent(). 40 | addClass('current') 41 | }); 42 | scrollToCurrentVarLink('.secondary .current'); 43 | } 44 | 45 | var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }()) 46 | 47 | function scrollPositionId(element) { 48 | var directory = window.location.href.replace(/[^\/]+\.html$/, '') 49 | return 'scroll::' + $(element).attr('id') + '::' + directory 50 | } 51 | 52 | function storeScrollPosition(element) { 53 | if (!hasStorage) return; 54 | localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft()) 55 | localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop()) 56 | } 57 | 58 | function recallScrollPosition(element) { 59 | if (!hasStorage) return; 60 | $(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x")) 61 | $(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y")) 62 | } 63 | 64 | function persistScrollPosition(element) { 65 | recallScrollPosition(element) 66 | $(element).scroll(function() { storeScrollPosition(element) }) 67 | } 68 | 69 | function sidebarContentWidth(element) { 70 | var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() }) 71 | return Math.max.apply(Math, widths) 72 | } 73 | 74 | function calculateSize(width, snap, margin, minimum) { 75 | if (width == 0) { 76 | return 0 77 | } 78 | else { 79 | return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2)) 80 | } 81 | } 82 | 83 | function resizeSidebars() { 84 | var primaryWidth = sidebarContentWidth('.primary') 85 | var secondaryWidth = 0 86 | 87 | if ($('.secondary').length != 0) { 88 | secondaryWidth = sidebarContentWidth('.secondary') 89 | } 90 | 91 | // snap to grid 92 | primaryWidth = calculateSize(primaryWidth, 32, 13, 160) 93 | secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160) 94 | 95 | $('.primary').css('width', primaryWidth) 96 | $('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1) 97 | 98 | if (secondaryWidth > 0) { 99 | $('#content').css('left', primaryWidth + secondaryWidth + 2) 100 | } 101 | else { 102 | $('#content').css('left', primaryWidth + 1) 103 | } 104 | } 105 | 106 | $(window).ready(resizeSidebars) 107 | $(window).ready(setCurrentVarLink) 108 | $(window).ready(function() { persistScrollPosition('.primary')}) 109 | $(window).ready(function() { 110 | $('#content').scroll(setCurrentVarLink) 111 | $(window).resize(setCurrentVarLink) 112 | $(window).resize(resizeSidebars) 113 | }) 114 | -------------------------------------------------------------------------------- /test/buttle/driver_test.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.driver-test 2 | (:require [clojure.test :refer :all] 3 | [buttle.driver-manager :as mgr] 4 | [buttle.util :as util] 5 | [buttle.proxy :as proxy] 6 | [buttle.driver :as drv])) 7 | 8 | (java.sql.DriverManager/setLogWriter 9 | (proxy [java.io.PrintWriter] [*out*] 10 | (println [s] 11 | (proxy-super println (str "buttle.driver-test -- DriverManager LOG: " s))))) 12 | 13 | (deftest test-parse-jdbc-url 14 | (is (= nil (drv/parse-jdbc-url "foobar"))) 15 | (is (= nil (drv/parse-jdbc-url "jdbc:buttle:"))) 16 | (is (= 42 (drv/parse-jdbc-url "jdbc:buttle:42"))) 17 | (is (thrown-with-msg? Throwable #"Could not parse URL" (drv/parse-jdbc-url "jdbc:buttle:(")))) 18 | 19 | (deftest test-accepts-url-fn 20 | (is (= nil (drv/accepts-url-fn "foobar"))) 21 | (is (= nil (drv/accepts-url-fn "jdbc:buttle:"))) 22 | (is (= {:target-url nil, :user nil, :class-for-name nil :password nil :datasource-spec nil} 23 | (drv/accepts-url-fn "jdbc:buttle:42"))) 24 | (is (thrown-with-msg? Throwable #"Could not parse URL" (drv/accepts-url-fn "jdbc:buttle:(")))) 25 | 26 | (def buttle-user (System/getenv "buttle_user")) 27 | (def buttle-password (System/getenv "buttle_password")) 28 | (def postgres-url "jdbc:postgresql://127.0.0.1:6632/postgres") 29 | 30 | (def buttle-url (format "jdbc:buttle:{:user %s :password %s :target-url %s}" 31 | (pr-str buttle-user) 32 | (pr-str buttle-password) 33 | (pr-str postgres-url))) 34 | 35 | (deftest test-connect-fn 36 | (is (= nil (drv/connect-fn "foobar" nil))) 37 | (is (= nil (drv/connect-fn "jdbc:buttle:" nil))) 38 | (is (thrown-with-msg? Throwable #"The url cannot be null" (drv/connect-fn "jdbc:buttle:42" nil)))) 39 | 40 | (deftest test-make-driver 41 | (let [buttle-driver (drv/make-driver) 42 | ;; Note: you CANNOT use a Clojure proxy here! The 43 | ;; DriverManager will not give back the registered driver 44 | ;; (bar-driver below) to you when calling getDriver - due to 45 | ;; the caller/classloader check in DriverManager. So we use 46 | ;; proxy/make-proxy instead (foo-driver below) which uses the 47 | ;; Java reflection API for creating the proxy. In this case 48 | ;; the interaction with DriverManager works as aspected. All 49 | ;; this is verified by the test cases here. 50 | bar-driver (proxy [java.sql.Driver] [] 51 | (toString [] 52 | "bar-driver")) 53 | foo-driver (proxy/make-proxy 54 | java.sql.Driver 55 | "foo-driver" 56 | (fn [the-method target-obj the-args] 57 | (condp = (.getName the-method) 58 | "acceptsURL" (boolean (re-matches #"foo:bar:.*" (first the-args))) 59 | "connect" (proxy [java.sql.Connection] [] 60 | (toString [] "foo-connection")))))] 61 | 62 | ;; In java.sql.DriverManager.getDriver(String) there is 63 | ;; 64 | ;; println(" skipping: " + aDriver.driver.getClass().getName()); 65 | ;; 66 | ;; which will print something like 67 | ;; 68 | ;; DriverManager LOG: DriverManager.getDriver("foobar") 69 | ;; DriverManager LOG: skipping: buttle.driver_test.proxy$java.lang.Object$Driver$1a6dd307 70 | ;; 71 | ;; In java.sql.DriverManager.getDrivers() and java.sql.DriverManager.getConnection(String, Properties, Class) 72 | ;; there is a bug: 73 | ;; 74 | ;; println(" skipping: " + aDriver.getClass().getName()); 75 | ;; 76 | ;; will print just "skipping: java.sql.DriverInfo" which is 77 | ;; probably not intended. 78 | 79 | (mgr/register-driver bar-driver) 80 | (is (thrown-with-msg? java.sql.SQLException #"No suitable driver" (mgr/get-driver "foobar"))) 81 | (is (= nil ((into #{} (mgr/get-drivers)) bar-driver))) 82 | 83 | (mgr/register-driver foo-driver) 84 | (try 85 | (is (= foo-driver ((into #{} (mgr/get-drivers)) foo-driver))) 86 | ;; go through local driver proxy 87 | (is (= "foo-connection" 88 | (str (.connect buttle-driver "jdbc:buttle:{:target-url \"foo:bar:fred\"}" nil)))) 89 | ;; go through registered Driver instance 90 | (is (= "foo-connection" 91 | (str (mgr/get-connection "jdbc:buttle:{:target-url \"foo:bar:fred\"}" "no-user" "no-password")))) 92 | (finally 93 | (java.sql.DriverManager/deregisterDriver foo-driver))))) 94 | 95 | #_ ;; this will be removed. 96 | (let [driver (buttle.jdbc.Driver.)] 97 | (proxy/def-handle [java.sql.Driver :buttle/acceptsURL] [the-method target-obj the-args] 98 | (do 99 | (.println System/out "bar") 100 | (proxy/handle-default the-method target-obj the-args))) 101 | (.acceptsURL driver "jdbc:buttle:42")) -------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.util.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.util documentation

buttle.util

Just some helpers.

->java-bean

(->java-bean class-spec props)

Creates and returns instance of class-typed class-spec and sets Java-Bean properties via setters from props map. Setter-method names are derived from map keys by camel-casing foo-bar to setFooBar. Map values have to be type-conforming (no conversion is done).

jndi-lookup

(jndi-lookup jndi)

Looks up entry in JNDI and returns it. Throws if entry cannot be found.

log

(log & xs)

with-tccl

macro

(with-tccl cl & body)

Thread-locally binds clojure.core/*use-context-classloader* to true, sets the current thread’s context classloader to cl and executes body within that context. On completion restores the context classloader and pops clojure.core/*use-context-classloader* to the previous value. Returns value of body evaluation.

-------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.driver-manager.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.driver-manager documentation

buttle.driver-manager

A thin/simple Clojure API around java.sql.DriverManager.

deregister-drivers

(deregister-drivers)

Iterates over all registered drivers (as of (get-drivers)) and deregisters each. Returns seq of drivers.

get-connection

(get-connection url user password)(get-connection url info)

Finds the driver for url and uses it to open a java.sql.Connection. Returns the connection or throws if anything goes wrong (see java.sql.DriverManager.getConnection(String, 4 | String, String)).

get-driver

(get-driver url)

Returns the registered java.sql.Driver which accepts the url. Throws if a driver cannot be found (see java.sql.DriverManager.getDriver(String)).

get-drivers

(get-drivers)

Returns a seq of all registered drivers (see java.sql.DriverManager.getDrivers())

register-driver

(register-driver driver)

Registers the driver (see java.sql.DriverManager.registerDriver(Driver)).

-------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.event.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.event documentation

buttle.event

Send events to consumers via clojure.core.async/chan.

4 |

Consumers can tap on event-mult to receive events that are produced through send-event. buttle.proxy/handle-default is such a producer.

5 |

Note that send-event synchronuosly puts events onto channel event-ch which is the input channel for event-mult. When there is no channel connected to event-mult (which is the case when this namespace is loaded the first time) calling send-event will not block (event-mult will just eat up those events). When there is one or more channels connected to event-mult (by consumers having called clojure.core.async/tap) calling send-event will block until the event has been sent/consumed by each of the connected channels. So make sure you have a go block consuming any channel that you connect to event-mult.

event-ch

For internal use only. The channel through which events are sent. Is the input for event-mult.

event-mult

API for consumers which want to receive events. Use clojure.core.async/tap to register your consumer clojure.core.async/chan

put-event

(put-event e)

For internal use only. Sends event e to event-ch.

send-event

(send-event e)

Synchronuosly (__blocking__) sends event e to event-ch (via put-event).

6 |

API for producing/sending events, which are then consumed through event-mult and conncted consumer channels (if present).

-------------------------------------------------------------------------------- /examples/buttle/examples/open_tracing.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.examples.open-tracing 2 | (:require [buttle.proxy :as proxy] 3 | [opentracing-clj.core :as tracing]) 4 | (:import [io.jaegertracing.spi Reporter] 5 | [io.jaegertracing.internal.reporters LoggingReporter CompositeReporter RemoteReporter$Builder] 6 | [io.jaegertracing.internal.samplers ConstSampler] 7 | [io.jaegertracing.thrift.internal.senders UdpSender] 8 | [io.jaegertracing.internal JaegerTracer$Builder])) 9 | 10 | ;; `root-span-name` will be presented as `Service` in the Jaeger UI. 11 | (defn build-jaeger-tracer [host root-span-name] 12 | (let [sender (UdpSender. host ;; note: UdpSender connects to host/port during construction! 13 | UdpSender/DEFAULT_AGENT_UDP_COMPACT_PORT ;; default port is 6831 14 | 0) ;; 0 means use ThriftUdpTransport#MAX_PACKET_SIZE 15 | remote-reporter (-> (doto (RemoteReporter$Builder.) 16 | (.withSender sender)) 17 | .build) 18 | ;; reporter sends and logs so when see what's going on 19 | logging-remote-reporter (CompositeReporter. 20 | (into-array Reporter [(LoggingReporter.) remote-reporter])) 21 | ;; sample (i.e. keep and report) *every* span 22 | sampler (ConstSampler. true)] 23 | (-> (doto (JaegerTracer$Builder. root-span-name) 24 | (.withSampler sampler) 25 | (.withReporter logging-remote-reporter)) 26 | .build))) 27 | 28 | ;; at the moment we're creating/using just one global Jaeger tracer 29 | (def jaeger-tracer 30 | (build-jaeger-tracer 31 | (System/getProperty "buttle_jaeger_agent_host") 32 | 33 | ;; this will be presented as "Service" in the Jaeger GUI -- Service 34 | ;; is the top-most (and mandatory -- no "all" option available) 35 | ;; qualification when searching for data/spans in the Jager GUI 36 | "buttle-trace")) 37 | 38 | ;; this will be presented as "Operation" in the Jaeger GUI -- 39 | ;; Operation is a secondary (and optional -- "all" option available) 40 | ;; qualification when searching for data/spans in the Jager GUI 41 | (defn method->operation [m] 42 | (format "%s/%s" 43 | (-> (.getDeclaringClass m) 44 | .getName) 45 | (.getName m))) 46 | 47 | ;; tags for all spans - search in Jaeger GUI with for example 48 | ;; `class-name=ResultSet method-name=wasNull` 49 | (defn invocation->tags [the-method target-obj the-args] 50 | {:method-name (.getName the-method) 51 | :method (format "%s/%s" 52 | (-> (.getDeclaringClass the-method) 53 | .getName) 54 | (.getName the-method)) 55 | :class-name (-> (.getDeclaringClass the-method) 56 | .getSimpleName) 57 | :class (-> (.getDeclaringClass the-method) 58 | .getName) 59 | :args (into [] the-args) 60 | ;; our app may have different _layers_ (like database, 61 | ;; business-logic) that we want to be able to do a search on. 62 | :layer "database"}) 63 | 64 | (defn throwable->tags [t] 65 | (loop [t t] 66 | (if-not (instance? java.lang.reflect.InvocationTargetException t) 67 | {:type 'throw 68 | :throw (pr-str t)} 69 | (recur (.getTargetException t))))) 70 | 71 | (defn result->tags [r] 72 | {:type 'return 73 | :return (pr-str r)}) 74 | 75 | (defn parse->vector [s] 76 | (try 77 | (read-string s) 78 | (catch Throwable t 79 | [(format "not a vector: '%s'" s)]))) 80 | 81 | (defn invoke-with-tracing [the-method target-obj the-args] 82 | (binding [tracing/*tracer* jaeger-tracer] 83 | (let [parent-path (parse->vector 84 | (or (some-> (.activeSpan tracing/*tracer*) 85 | (.getBaggageItem "path")) 86 | "[]"))] 87 | (tracing/with-span [s {:name (method->operation the-method)}] 88 | (tracing/set-tags (invocation->tags the-method target-obj the-args)) 89 | (tracing/set-baggage-item "path" (pr-str (conj parent-path (str s)))) 90 | (let [r (try 91 | (proxy/handle-default the-method target-obj the-args) 92 | (catch Throwable t 93 | (do 94 | (tracing/set-tags (throwable->tags t)) 95 | (throw t))))] 96 | (tracing/set-tags (result->tags r)) 97 | r))))) 98 | 99 | #_ ;; trace all/any class/method 100 | (defmethod proxy/handle :default [the-method target-obj the-args] 101 | (invoke-with-tracing the-method target-obj the-args)) 102 | 103 | (proxy/def-handle [java.sql.Driver :buttle/default] [the-method target-obj the-args] 104 | (invoke-with-tracing the-method target-obj the-args)) 105 | 106 | (proxy/def-handle [java.sql.Connection :buttle/default] [the-method target-obj the-args] 107 | (invoke-with-tracing the-method target-obj the-args)) 108 | 109 | (proxy/def-handle [java.sql.Statement :buttle/default] [the-method target-obj the-args] 110 | (invoke-with-tracing the-method target-obj the-args)) 111 | 112 | ;; Note that there are two interfaces that javax.sql.DataSource 113 | ;; extends (javax.sql.CommonDataSource, java.sql.Wrapper), so we have 114 | ;; to name them 115 | 116 | (proxy/def-handle [javax.sql.DataSource :buttle/default] [the-method target-obj the-args] 117 | (invoke-with-tracing the-method target-obj the-args)) 118 | 119 | ;; Note: getConnection is defined in CommonDataSource *and* Wrapper! 120 | ;; For some reason we get the following exception when calling 121 | ;; javax.sql.DataSource/getConnection: 122 | ;; 123 | ;; Multiple methods in multimethod 'handle' match dispatch value: 124 | ;; [javax.sql.DataSource :buttle/getConnection] -> 125 | ;; [javax.sql.CommonDataSource :buttle/default] 126 | ;; [java.sql.Wrapper :buttle/default], 127 | ;; 128 | ;; There must be a bug in buttle.proxy. For now we workaround this by 129 | ;; def-handling this invocation key explicitly. 130 | (proxy/def-handle [javax.sql.DataSource :buttle/getConnection] [the-method target-obj the-args] 131 | (invoke-with-tracing the-method target-obj the-args)) 132 | 133 | (proxy/def-handle [javax.sql.CommonDataSource :buttle/default] [the-method target-obj the-args] 134 | (invoke-with-tracing the-method target-obj the-args)) 135 | 136 | (proxy/def-handle [java.sql.Wrapper :buttle/default] [the-method target-obj the-args] 137 | (invoke-with-tracing the-method target-obj the-args)) 138 | -------------------------------------------------------------------------------- /src/buttle/data_source.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.data-source 2 | "The _Buttle_ `javax.sql.DataSource`. 3 | 4 | This namespace delivers `buttle.jdbc.DataSource` via 5 | `:gen-class`. This named class can be used as a datasource class for 6 | application servers." 7 | 8 | (:import [javax.sql DataSource] 9 | [java.sql SQLException] 10 | [java.sql SQLFeatureNotSupportedException]) 11 | (:require [buttle.proxy :as proxy] 12 | [buttle.util :as util])) 13 | 14 | (definterface ButtleDataSource 15 | (^void setDelegateSpec [^String spec])) 16 | 17 | (gen-class 18 | :init init 19 | :state state 20 | :name buttle.jdbc.DataSource 21 | :extends buttle.SetContextClassLoaderInStaticInitializer 22 | :implements [javax.sql.DataSource 23 | buttle.data_source.ButtleDataSource]) 24 | 25 | (defn spec->type 26 | "Dispatch for `retrieve-data-soure`. Returns type of `spec` (`:jndi` 27 | for `String`, `:ds-class` for maps)." 28 | 29 | [spec] 30 | (cond 31 | (string? spec) :jndi 32 | (map? spec) :ds-class 33 | :else (format "Unknown spec '%s'" (pr-str spec)))) 34 | 35 | (defmulti retrieve-data-soure 36 | "Factory/lookup for _real_ datasource. `String` arg will be expected 37 | to be JNDI name of a `javax.sql.DataSource`. In this case the 38 | datasource will be looked up in JNDI. If the arg is a map the 39 | class-typed `:delegate-class` will be used to create an instance and 40 | then all remaining keys/values will be used to set the instance's 41 | Java-Bean properties." 42 | 43 | #'spec->type) 44 | 45 | ;; Create instance and invoke setters. Setter-names are derived from 46 | ;; map keys by camel-casing `:foo-bar` to `setFooBar`. 47 | (defmethod retrieve-data-soure :ds-class [ds-class-spec] 48 | (util/->java-bean (:delegate-class ds-class-spec) 49 | (dissoc ds-class-spec :delegate-class))) 50 | 51 | ;; Retrieve datasource and check that it *really IS* a 52 | ;; datasource. Else we won't be able to delegate to it. 53 | (defmethod retrieve-data-soure :jndi [jndi-spec] 54 | (let [ds (util/jndi-lookup jndi-spec)] 55 | (when-not (isa? (.getClass ds) javax.sql.DataSource) 56 | (throw 57 | (RuntimeException. 58 | (format "This is not a javax.sql.DataSource: '%s' It implements these interfaces: '%s'" 59 | ds (->> ds .getClass .getInterfaces (into [])))))) 60 | ds)) 61 | 62 | ;; ---------------------------------------------------------------------------------------------------------------------- 63 | ;; A note on classloading 64 | ;; 65 | ;; See src/buttle/connection_pool_data_source.clj for details. 66 | ;; ---------------------------------------------------------------------------------------------------------------------- 67 | 68 | (defn make-data-source 69 | "Creates the _Buttle_ datasource." 70 | 71 | [] 72 | (let [ds-spec (atom nil) 73 | ds (atom nil) 74 | ;; classloader of cached proxy class definition 75 | cl (-> (proxy [DataSource ButtleDataSource] []) 76 | .getClass 77 | .getClassLoader) 78 | ;; lazy creation and cache 79 | ds! (fn [] (or @ds 80 | (reset! ds 81 | (retrieve-data-soure @ds-spec))))] 82 | 83 | ;; make Java dyn. proxy D which uses cl as its definiting classloader 84 | (proxy/make-proxy 85 | [(Class/forName "buttle.data_source.ButtleDataSource" true cl) DataSource] 86 | 87 | ;; proxy should have classloader cl 88 | (proxy [DataSource ButtleDataSource] [] 89 | (setDelegateSpec [spec] 90 | (try 91 | (util/with-tccl (.getClassLoader (Class/forName "buttle.jdbc.DataSource")) 92 | (reset! ds-spec 93 | (-> spec 94 | (.replaceAll "[\\n\\t\\r]+" " ") 95 | read-string 96 | eval))) 97 | (catch Throwable t 98 | (throw (ex-info "Could not parse spec" {:spec spec} t))))) 99 | (getConnection [& [user password :as xs]] 100 | (if-not xs (-> (ds!) .getConnection) 101 | (-> (ds!) (.getConnection user password)))) 102 | (getLogWriter [] 103 | (-> (ds!) .getLogWriter)) 104 | (setLogWriter [pr-wrt] 105 | (-> (ds!) (.setLogWriter pr-wrt))) 106 | (setLoginTimeout [sec] 107 | (-> (ds!) (.setLoginTimeout sec))) 108 | (getLoginTimeout [] 109 | (-> (ds!) .getLoginTimeout)) 110 | (getParentLogger [] 111 | (-> (ds!) .getParentLogger)) 112 | (unwrap [ifc] 113 | (-> (ds!) (.unwrap ifc))) 114 | (isWrapperFor [ifc] 115 | (-> (ds!) (.isWrapperFor ifc)))) 116 | proxy/handle))) 117 | 118 | (defn -init 119 | "Constructor function of `buttle.jdbc.DataSource`." 120 | 121 | [] 122 | [[] (make-data-source)]) 123 | 124 | (defn -setDelegateSpec 125 | "Sets the `delegateSpec` of the _Buttle_ datasource." 126 | 127 | [this spec] 128 | (.setDelegateSpec (.state this) spec)) 129 | 130 | (defn -getConnection 131 | "Implements `javax.sql.DataSource/getConnection`. Just delegates to 132 | the referenced/internal datasource (see `-init`)." 133 | 134 | ([this] 135 | (.getConnection (.state this))) 136 | ([this username password] 137 | (.getConnection (.state this) username password))) 138 | 139 | (defn -getLogWriter 140 | "Implements `javax.sql.CommonDataSource/getLogWriter`. Just 141 | delegates to the referenced/internal datasource (see `-init`)." 142 | 143 | [this] 144 | (.getLogWriter (.state this))) 145 | 146 | (defn -setLogWriter 147 | "Implements `javax.sql.CommonDataSource/setLogWriter`. Just 148 | delegates to the referenced/internal datasource (see `-init`)." 149 | 150 | [this pr-wrt] 151 | (.setLogWriter (.state this) pr-wrt)) 152 | 153 | (defn -setLoginTimeout 154 | "Implements `javax.sql.CommonDataSource/setLoginTimeout`. Just 155 | delegates to the referenced/internal datasource (see `-init`)." 156 | 157 | [this sec] 158 | (.setLoginTimeout (.state this) sec)) 159 | 160 | (defn -getLoginTimeout 161 | "Implements `javax.sql.CommonDataSource/getLoginTimeout`. Just 162 | delegates to the referenced/internal datasource (see `-init`)." 163 | 164 | [this] 165 | (.getLoginTimeout (.state this))) 166 | 167 | (defn -getParentLogger 168 | "Implements `javax.sql.CommonDataSource/getParentLogger`. Just 169 | delegates to the referenced/internal datasource (see `-init`)." 170 | 171 | [this] 172 | (.getParentLogger (.state this))) 173 | 174 | (defn -unwrap 175 | "Implements `java.sql.Wrapper/unwrap`. Just delegates to the 176 | referenced/internal datasource (see `-init`)." 177 | 178 | [this ifc] 179 | (.unwrap (.state this) ifc)) 180 | 181 | (defn -isWrapperFor 182 | "Implements `java.sql.Wrapper/isWrapperFor`. Just delegates to the 183 | referenced/internal datasource (see `-init`)." 184 | 185 | [this ifc] 186 | (.isWrapperFor (.state this) ifc)) 187 | -------------------------------------------------------------------------------- /test/buttle/event_test.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.event-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.core.async :as a] 4 | [buttle.proxy :as proxy] 5 | [buttle.event :as event])) 6 | 7 | ;; ---------------------------------------------------------------------------------- 8 | ;; For all the tests I use a promise to synchronize the 9 | ;; event-receiving part with the test-driving (send event and compare 10 | ;; values) part. 11 | ;; 12 | ;; This test is most simple. Just tap a channel onto 13 | ;; `event/event-mult`, go-consume one value from that channel and 14 | ;; deliver it to the test-driving thread. Close and untap (which we do 15 | ;; not really need here). The test-driver just sends an event and 16 | ;; checks that it receives it through the promise. 17 | ;; ---------------------------------------------------------------------------------- 18 | 19 | (deftest produce-and-consume-event-test 20 | (let [p (promise)] 21 | (let [ch (a/chan)] 22 | (a/tap event/event-mult ch) 23 | (a/go 24 | (when-let [e (a/ %s" x %)) true)]} 50 | (cond 51 | (vector? x) :vector 52 | (map? x) :map 53 | (keyword? x) :echo 54 | :else :else)) 55 | 56 | (defmulti project-event #'type-of) 57 | 58 | (defmethod project-event :default [x] 59 | (throw (RuntimeException. (format "Unknown event : %s" (pr-str x))))) 60 | 61 | (defmethod project-event :echo [x] 62 | x) 63 | 64 | (defmethod project-event :vector [x] 65 | (into [] (map project-event x))) 66 | 67 | (defmethod project-event :map [x] 68 | ;;{:post [(or (.println System/out (format "(project-event :map %s) --> %s" x %)) true)]} 69 | (condp = (:type x) 70 | nil x 71 | :invoke (map-map x {:ts "TS" :thread "THREAD"}) 72 | :return (map-map x {:ts "TS" :dur-msec "DUR-MSEC" :invoke "INVOKE"}) 73 | :throw (map-map x {:ts "TS" :dur-msec "DUR-MSEC" :invoke "INVOKE" :throw "THROW"}))) 74 | 75 | (deftest project-event-test 76 | (let [i-evt {:type :invoke :ts 1}] 77 | (is (= {:type :invoke :ts "TS"} 78 | (project-event i-evt))))) 79 | 80 | (deftest event-factory-test 81 | (let [i-evt (proxy/->invoke-event (.getMethod java.sql.Connection "getCatalog" nil) "foo" nil) 82 | r-evt (proxy/->return-event i-evt "foo") 83 | t-evt (proxy/->throw-event i-evt (RuntimeException. "oops"))] 84 | (is (= (project-event i-evt) 85 | {:type :invoke, 86 | :invoke :java.sql.Connection/getCatalog, 87 | :args [], 88 | :thread "THREAD" 89 | :ts "TS"})) 90 | (is (= (project-event r-evt) 91 | {:type :return, 92 | :invoke "INVOKE", 93 | :return "foo", 94 | :ts "TS", 95 | :dur-msec "DUR-MSEC"})) 96 | (is (= (project-event t-evt) 97 | {:type :throw, 98 | :invoke "INVOKE", 99 | :throw "THROW", 100 | :ts "TS", 101 | :dur-msec "DUR-MSEC"})))) 102 | 103 | ;; ---------------------------------------------------------------------------------- 104 | ;; Helper function that pulls events out of event/event-mult and 105 | ;; conjoins them to bag ref until :done event. Returns the channel. 106 | ;; 107 | ;; See invoke-return-test (possible bug here). 108 | ;; ---------------------------------------------------------------------------------- 109 | 110 | (defn consume-until-done [prms bag] 111 | (let [ch (a/chan)] 112 | (a/tap event/event-mult ch) 113 | (a/go 114 | (loop [] 115 | (when-let [e (a/type 25 | "Dispatch for `retrieve-cp-data-soure`. Returns type of 26 | `spec` (`:jndi` for `String`, `:cp-class` for maps)." 27 | 28 | [spec] 29 | (cond 30 | (string? spec) :jndi 31 | (map? spec) :cp-class 32 | :else (format "Unknown spec '%s'" (pr-str spec)))) 33 | 34 | (defmulti retrieve-cp-data-soure 35 | "Factory/lookup for _real_ CP-datasource. `String` arg will be 36 | expected to be JNDI name of a 37 | `javax.sql.ConnectionPoolDataSource`. In this case the CP-datasource 38 | will be looked up in JNDI. If the arg is a map the class-typed 39 | `:delegate-class` will be used to create an instance and then all 40 | remaining keys/values will be used to set the instance's Java-Bean 41 | properties." 42 | 43 | #'spec->type) 44 | 45 | (defmethod retrieve-cp-data-soure :jndi [jndi-spec] 46 | (let [cp-ds (util/jndi-lookup jndi-spec)] 47 | (when-not (isa? (.getClass cp-ds) ConnectionPoolDataSource) 48 | (throw 49 | (RuntimeException. 50 | (format "This is not a javax.sql.ConnectionPoolDataSource: '%s' It implements these interfaces: '%s'" 51 | cp-ds (->> cp-ds .getClass .getInterfaces (into [])))))) 52 | cp-ds)) 53 | 54 | (defmethod retrieve-cp-data-soure :cp-class [cp-class-spec] 55 | (let [clazz (:delegate-class cp-class-spec)] 56 | (when-not (isa? clazz ConnectionPoolDataSource) 57 | (throw 58 | (RuntimeException. 59 | (format "This is not a javax.sql.ConnectionPoolDataSource: '%s' It implements these interfaces: '%s'" 60 | clazz (->> clazz .getInterfaces (into [])))))) 61 | (util/->java-bean clazz (dissoc cp-class-spec :delegate-class)))) 62 | 63 | ;; ---------------------------------------------------------------------------------------------------------------------- 64 | ;; A note on classloading 65 | ;; 66 | ;; buttle.proxy/make-proxy creates a Java dyn. proxy D which 67 | ;; reflectivly calls methods M on the Clojure proxy P that's created 68 | ;; below. 69 | ;; 70 | ;; This only works if D's class definition matches P's class 71 | ;; definition. Otherwise M of D can not be called on P. 72 | ;; 73 | ;; P is defined by Clojure's dynamic claassloader. So we have to make 74 | ;; D be defined by them same classloader. Clojure's proxy classes are 75 | ;; cached by key (which is [ConnectionPoolDataSource 76 | ;; ButtleCpDataSource] below) so we calculate it's classloader cl and 77 | ;; then use it to load class/interface 78 | ;; buttle.connection_pool_data_source.ButtleCpDataSource. 79 | ;; buttle.proxy/make-proxy uses the first argument's classloader to 80 | ;; define D. This makes D and P being compatible and M can be called. 81 | ;; 82 | ;; Only for IBM WAS I had to do this. Wildfly's classloaders work 83 | ;; different somehow. 84 | ;; ---------------------------------------------------------------------------------------------------------------------- 85 | 86 | (defn make-cp-data-source 87 | "Creates and returns a _Buttle_ `javax.sql.ConnectionPoolDataSource`. 88 | 89 | Use `setDelegateSpec` to control what the _real_ (or _backing_) 90 | `javax.sql.ConnectionPoolDataSource` is. You can use `String` to use 91 | a CP-datasource from JNDI. Use a map to create an instance and set 92 | properties (see `retrieve-cp-data-soure` for details). 93 | 94 | Note: creation is done on-demand when the backing CP-datasource is 95 | actually needed/used." 96 | 97 | [] 98 | (let [cp-ds-spec (atom nil) 99 | cp-ds (atom nil) 100 | ;; classloader of cached proxy class definition 101 | cl (-> (proxy [ConnectionPoolDataSource ButtleCpDataSource] []) 102 | .getClass 103 | .getClassLoader) 104 | ;; lazy creation and cache 105 | cp-ds! (fn [] (or @cp-ds 106 | (reset! cp-ds 107 | (retrieve-cp-data-soure @cp-ds-spec))))] 108 | 109 | ;; make Java dyn. proxy D which uses cl as its definiting classloader 110 | (proxy/make-proxy 111 | [(Class/forName "buttle.connection_pool_data_source.ButtleCpDataSource" true cl) ConnectionPoolDataSource] 112 | 113 | ;; proxy should have classloader cl 114 | (proxy [ConnectionPoolDataSource ButtleCpDataSource] [] 115 | (setDelegateSpec [spec] 116 | (try 117 | (util/with-tccl (.getClassLoader (Class/forName "buttle.jdbc.ConnectionPoolDataSource")) 118 | (reset! cp-ds-spec 119 | (-> spec 120 | (.replaceAll "[\\n\\t\\r]+" " ") 121 | read-string 122 | eval))) 123 | (catch Throwable t 124 | (throw (ex-info "Could not parse spec" {:spec spec} t))))) 125 | (getPooledConnection [& [user password :as xs]] 126 | (if-not xs (-> (cp-ds!) .getPooledConnection) 127 | (-> (cp-ds!) (.getPooledConnection user password)))) 128 | (getLogWriter [] 129 | (-> (cp-ds!) .getLogWriter)) 130 | (setLogWriter [pr-wrt] 131 | (-> (cp-ds!) (.setLogWriter pr-wrt))) 132 | (setLoginTimeout [sec] 133 | (-> (cp-ds!) (.setLoginTimeout sec))) 134 | (getLoginTimeout [] 135 | (-> (cp-ds!) .getLoginTimeout)) 136 | (getParentLogger [] 137 | (-> (cp-ds!) .getParentLogger))) 138 | (fn [the-method target-obj the-args] 139 | ;; set TCCL so that CLojure RT/Compiler uses correct 140 | ;; classloader. 141 | (util/with-tccl (.getClassLoader (Class/forName "buttle.jdbc.ConnectionPoolDataSource")) 142 | (proxy/handle the-method target-obj the-args)))))) 143 | 144 | (defn -init 145 | "Constructor function of 146 | `buttle.jdbc.ConnectionPoolDataSource`. Calls `make-cp-data-source` 147 | and initialized `state` with that (i.e. the _Buttle_ CP-datasource 148 | is cached)." 149 | 150 | [] 151 | [[] (make-cp-data-source)]) 152 | 153 | (defn -setDelegateSpec 154 | "Implements 155 | `buttle.connection_pool_data_source.ButtleCpDataSource/setDelegateSpec`. Just 156 | delegates to the referenced/internal _Buttle_ CP-datasource (see 157 | `-init`)." 158 | 159 | [this spec] 160 | (.setDelegateSpec (.state this) spec)) 161 | 162 | (defn -getPooledConnection 163 | "Implements 164 | `javax.sql.ConnectionPoolDataSource/getPooledConnection`. Just 165 | delegates to the referenced/internal CP-datasource (see `-init`)." 166 | 167 | ([this] 168 | (.getPooledConnection (.state this))) 169 | ([this username password] 170 | (.getPooledConnection (.state this) username password))) 171 | 172 | (defn -getLogWriter 173 | "Implements `javax.sql.CommonDataSource/getLogWriter`. Just 174 | delegates to the referenced/internal CP-datasource (see `-init`)." 175 | 176 | [this] 177 | (.getLogWriter (.state this))) 178 | 179 | (defn -setLogWriter 180 | "Implements `javax.sql.CommonDataSource/setLogWriter`. Just 181 | delegates to the referenced/internal CP-datasource (see `-init`)." 182 | 183 | [this pr-wrt] 184 | (.setLogWriter (.state this) pr-wrt)) 185 | 186 | (defn -setLoginTimeout 187 | "Implements `javax.sql.CommonDataSource/setLoginTimeout`. Just 188 | delegates to the referenced/internal CP-datasource (see `-init`)." 189 | 190 | [this sec] 191 | (.setLoginTimeout (.state this) sec)) 192 | 193 | (defn -getLoginTimeout 194 | "Implements `javax.sql.CommonDataSource/getLoginTimeout`. Just 195 | delegates to the referenced/internal CP-datasource (see `-init`)." 196 | 197 | [this] 198 | (.getLoginTimeout (.state this))) 199 | 200 | (defn -getParentLogger 201 | "Implements `javax.sql.CommonDataSource/getParentLogger`. Just 202 | delegates to the referenced/internal CP-datasource (see `-init`)." 203 | 204 | [this] 205 | (.getParentLogger (.state this))) 206 | 207 | -------------------------------------------------------------------------------- /src/buttle/xa_data_source.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.xa-data-source 2 | "The _Buttle_ `javax.sql.XADataSource`. 3 | 4 | This namespace delivers `buttle.jdbc.XADataSource` via 5 | `:gen-class`. This named class can be used as an XA-datasource class 6 | for application servers. 7 | 8 | __Example (Wildfly)__: 9 | 10 | 11 | 12 | buttle.jdbc.XADataSource 13 | buttle-driver 14 | 15 | postgres-user 16 | postgres-password 17 | 18 | 19 | {:delegate-class org.postgresql.xa.PGXADataSource 20 | :url \"jdbc:postgresql://127.0.0.1:6632/postgres\"} 21 | 22 | " 23 | 24 | (:import [javax.sql XADataSource]) 25 | (:require [buttle.proxy :as proxy] 26 | [buttle.driver] 27 | [buttle.util :as util])) 28 | 29 | ;; Needed for -setDelegateSpec. Otherwise the compile won't generate 30 | ;; the method for buttle.jdbc.XADataSource 31 | (definterface ButtleDataSource 32 | (^void setDelegateSpec [^String spec])) 33 | 34 | ;; Cannot be part of `ns` form because references 35 | ;; `buttle.xa_data_source.ButtleDataSource` from above 36 | (gen-class 37 | :init init 38 | :state state 39 | :name buttle.jdbc.XADataSource 40 | :extends buttle.SetContextClassLoaderInStaticInitializer 41 | :implements [javax.sql.XADataSource 42 | buttle.xa_data_source.ButtleDataSource]) 43 | 44 | (defn spec->type 45 | "Dispatch for `retrieve-xa-data-soure`. Returns type of 46 | `spec` (`:jndi` for `String`, `:xa-class` for maps)." 47 | 48 | [spec] 49 | (cond 50 | (string? spec) :jndi 51 | (map? spec) :xa-class 52 | :else (format "Unknown spec '%s'" (pr-str spec)))) 53 | 54 | (defmulti retrieve-xa-data-soure 55 | "Factory/lookup for _real_ XA-datasource. `String` arg will be 56 | expected to be JNDI name of a `javax.sql.XADataSource`. In this case 57 | the XA-datasource will be looked up in JNDI. If the arg is a map the 58 | class-typed `:delegate-class` will be used to create an instance and 59 | then all remaining keys/values will be used to set the instance's 60 | Java-Bean properties." 61 | 62 | #'spec->type) 63 | 64 | ;; Retrieve XA-datasource and check that it *really IS* an 65 | ;; XA-datasource. Else we won't be able to delegate to it. 66 | (defmethod retrieve-xa-data-soure :jndi [jndi-spec] 67 | (let [xa-ds (util/jndi-lookup jndi-spec)] 68 | (when-not (isa? (.getClass xa-ds) javax.sql.XADataSource) 69 | (throw 70 | (RuntimeException. 71 | (format (str "(retrieve-xa-data-soure %s) fails!" 72 | " This is not a javax.sql.XADataSource: '%s'" 73 | " It implements these interfaces: '%s'") 74 | (pr-str jndi-spec) 75 | xa-ds 76 | (->> xa-ds .getClass .getInterfaces (into [])))))) 77 | xa-ds)) 78 | 79 | ;; Create instance and invoke setters. Setter-names are derived from 80 | ;; map keys by camel-casing `:foo-bar` to `setFooBar`. 81 | (defmethod retrieve-xa-data-soure :xa-class [{:keys [delegate-class] :as xa-class-spec}] 82 | (when (nil? delegate-class) 83 | (throw (RuntimeException. 84 | (format "No :delegate-class given: %s" xa-class-spec)))) 85 | (util/->java-bean delegate-class 86 | (dissoc xa-class-spec :delegate-class))) 87 | 88 | ;; ---------------------------------------------------------------------------------------------------------------------- 89 | ;; A note on classloading 90 | ;; 91 | ;; See src/buttle/connection_pool_data_source.clj for details. 92 | ;; ---------------------------------------------------------------------------------------------------------------------- 93 | 94 | (defn make-xa-data-source 95 | "Creates and returns a _Buttle_ `javax.sql.XADataSource`. 96 | 97 | Use `setDelegateSpec` to control what the _real_ (or _backing_) 98 | `javax.sql.XADataSource` is. You can use `String` to use an 99 | XA-datasource from JNDI. Use a map to create an instance and set 100 | properties (see `retrieve-xa-data-soure` for details). 101 | 102 | Note: creation is done on-demand when the backing XA-datasource is 103 | actually needed/used." 104 | 105 | [] 106 | (let [xa-ds-spec (atom nil) 107 | xa-ds (atom nil) 108 | ;; classloader of cached proxy class definition 109 | cl (-> (proxy [XADataSource ButtleDataSource] []) 110 | .getClass 111 | .getClassLoader) 112 | ;; lazy creation and cache 113 | xa-ds! (fn [] (or @xa-ds 114 | (reset! xa-ds 115 | (retrieve-xa-data-soure @xa-ds-spec))))] 116 | 117 | ;; make Java dyn. proxy D which uses cl as its definiting classloader 118 | (proxy/make-proxy 119 | [(Class/forName "buttle.xa_data_source.ButtleDataSource" true cl) XADataSource] 120 | 121 | ;; proxy should have classloader cl 122 | (proxy [XADataSource ButtleDataSource] [] 123 | (setDelegateSpec [spec] 124 | (try 125 | (util/with-tccl (.getClassLoader (Class/forName "buttle.jdbc.XADataSource")) 126 | (reset! xa-ds-spec 127 | (-> spec 128 | (.replaceAll "[\\n\\t\\r]+" " ") 129 | read-string 130 | eval))) 131 | (catch Throwable t 132 | (throw (ex-info "Could not parse spec" {:spec spec} t))))) 133 | (getXAConnection [& [user password :as xs]] 134 | (if-not xs (-> (xa-ds!) .getXAConnection) 135 | (-> (xa-ds!) (.getXAConnection user password)))) 136 | (getLogWriter [] 137 | (-> (xa-ds!) .getLogWriter)) 138 | (setLogWriter [pr-wrt] 139 | (-> (xa-ds!) (.setLogWriter pr-wrt))) 140 | (setLoginTimeout [sec] 141 | (-> (xa-ds!) (.setLoginTimeout sec))) 142 | (getLoginTimeout [] 143 | (-> (xa-ds!) .getLoginTimeout)) 144 | (getParentLogger [] 145 | (-> (xa-ds!) .getParentLogger))) 146 | (fn [the-method target-obj the-args] 147 | ;; set TCCL so that CLojure RT/Compiler uses correct 148 | ;; classloader. 149 | (util/with-tccl (.getClassLoader (Class/forName "buttle.jdbc.XADataSource")) 150 | (proxy/handle the-method target-obj the-args)))))) 151 | 152 | (defn -init 153 | "Constructor function of `buttle.jdbc.XADataSource`. Calls 154 | `make-xa-data-source` and initialized `state` with that (i.e. the 155 | _Buttle_ XA-datasource is cached)." 156 | 157 | [] 158 | [[] (make-xa-data-source)]) 159 | 160 | (defn -setDelegateSpec 161 | "Implements 162 | `buttle.xa_data_source.ButtleDataSource/setDelegateSpec`. Just 163 | delegates to the referenced/internal _Buttle_ XA-datasource (see 164 | `-init`)." 165 | 166 | [this spec] 167 | (.setDelegateSpec (.state this) spec)) 168 | 169 | (defn -getXAConnection 170 | "Implements `javax.sql.XADataSource/getXAConnection`. Just delegates 171 | to the referenced/internal XA-datasource (see `-init`)." 172 | 173 | ([this] 174 | (.getXAConnection (.state this))) 175 | ([this username password] 176 | (.getXAConnection (.state this) username password))) 177 | 178 | (defn -getLogWriter 179 | "Implements `javax.sql.CommonDataSource/getLogWriter`. Just 180 | delegates to the referenced/internal XA-datasource (see `-init`)." 181 | 182 | [this] 183 | (.getLogWriter (.state this))) 184 | 185 | (defn -setLogWriter 186 | "Implements `javax.sql.CommonDataSource/setLogWriter`. Just 187 | delegates to the referenced/internal XA-datasource (see `-init`)." 188 | 189 | [this pr-wrt] 190 | (.setLogWriter (.state this) pr-wrt)) 191 | 192 | (defn -setLoginTimeout 193 | "Implements `javax.sql.CommonDataSource/setLoginTimeout`. Just 194 | delegates to the referenced/internal XA-datasource (see `-init`)." 195 | 196 | [this sec] 197 | (.setLoginTimeout (.state this) sec)) 198 | 199 | (defn -getLoginTimeout 200 | "Implements `javax.sql.CommonDataSource/getLoginTimeout`. Just 201 | delegates to the referenced/internal XA-datasource (see `-init`)." 202 | 203 | [this] 204 | (.getLoginTimeout (.state this))) 205 | 206 | (defn -getParentLogger 207 | "Implements `javax.sql.CommonDataSource/getParentLogger`. Just 208 | delegates to the referenced/internal XA-datasource (see `-init`)." 209 | 210 | [this] 211 | (.getParentLogger (.state this))) 212 | 213 | -------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.data-source.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.data-source documentation

buttle.data-source

The Buttle javax.sql.DataSource.

4 |

This namespace delivers buttle.jdbc.DataSource via :gen-class. This named class can be used as a datasource class for application servers.

-getConnection

(-getConnection this)(-getConnection this username password)

Implements javax.sql.DataSource/getConnection. Just delegates to the referenced/internal datasource (see -init).

-getLoginTimeout

(-getLoginTimeout this)

Implements javax.sql.CommonDataSource/getLoginTimeout. Just delegates to the referenced/internal datasource (see -init).

-getLogWriter

(-getLogWriter this)

Implements javax.sql.CommonDataSource/getLogWriter. Just delegates to the referenced/internal datasource (see -init).

-getParentLogger

(-getParentLogger this)

Implements javax.sql.CommonDataSource/getParentLogger. Just delegates to the referenced/internal datasource (see -init).

-init

(-init)

Constructor function of buttle.jdbc.DataSource.

-isWrapperFor

(-isWrapperFor this ifc)

Implements java.sql.Wrapper/isWrapperFor. Just delegates to the referenced/internal datasource (see -init).

-setDelegateSpec

(-setDelegateSpec this spec)

Sets the delegateSpec of the Buttle datasource.

-setLoginTimeout

(-setLoginTimeout this sec)

Implements javax.sql.CommonDataSource/setLoginTimeout. Just delegates to the referenced/internal datasource (see -init).

-setLogWriter

(-setLogWriter this pr-wrt)

Implements javax.sql.CommonDataSource/setLogWriter. Just delegates to the referenced/internal datasource (see -init).

-unwrap

(-unwrap this ifc)

Implements java.sql.Wrapper/unwrap. Just delegates to the referenced/internal datasource (see -init).

make-data-source

(make-data-source)

Creates the Buttle datasource.

retrieve-data-soure

multimethod

Factory/lookup for real datasource. String arg will be expected to be JNDI name of a javax.sql.DataSource. In this case the datasource will be looked up in JNDI. If the arg is a map the class-typed :delegate-class will be used to create an instance and then all remaining keys/values will be used to set the instance’s Java-Bean properties.

spec->type

(spec->type spec)

Dispatch for retrieve-data-soure. Returns type of spec (:jndi for String, :ds-class for maps).

-------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.connection-pool-data-source.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.connection-pool-data-source documentation

buttle.connection-pool-data-source

The Buttle javax.sql.ConnectionPoolDataSource (CP-datasource).

4 |

This namespace delivers buttle.jdbc.ConnectionPoolDataSource via :gen-class. This named class can be used as a CP-datasource class for application servers.

-getLoginTimeout

(-getLoginTimeout this)

Implements javax.sql.CommonDataSource/getLoginTimeout. Just delegates to the referenced/internal CP-datasource (see -init).

-getLogWriter

(-getLogWriter this)

Implements javax.sql.CommonDataSource/getLogWriter. Just delegates to the referenced/internal CP-datasource (see -init).

-getParentLogger

(-getParentLogger this)

Implements javax.sql.CommonDataSource/getParentLogger. Just delegates to the referenced/internal CP-datasource (see -init).

-getPooledConnection

(-getPooledConnection this)(-getPooledConnection this username password)

Implements javax.sql.ConnectionPoolDataSource/getPooledConnection. Just delegates to the referenced/internal CP-datasource (see -init).

-init

(-init)

Constructor function of buttle.jdbc.ConnectionPoolDataSource. Calls make-cp-data-source and initialized state with that (i.e. the Buttle CP-datasource is cached).

-setDelegateSpec

(-setDelegateSpec this spec)

Implements buttle.connection_pool_data_source.ButtleCpDataSource/setDelegateSpec. Just delegates to the referenced/internal Buttle CP-datasource (see -init).

-setLoginTimeout

(-setLoginTimeout this sec)

Implements javax.sql.CommonDataSource/setLoginTimeout. Just delegates to the referenced/internal CP-datasource (see -init).

-setLogWriter

(-setLogWriter this pr-wrt)

Implements javax.sql.CommonDataSource/setLogWriter. Just delegates to the referenced/internal CP-datasource (see -init).

make-cp-data-source

(make-cp-data-source)

Creates and returns a Buttle javax.sql.ConnectionPoolDataSource.

5 |

Use setDelegateSpec to control what the real (or backing) javax.sql.ConnectionPoolDataSource is. You can use String to use a CP-datasource from JNDI. Use a map to create an instance and set properties (see retrieve-cp-data-soure for details).

6 |

Note: creation is done on-demand when the backing CP-datasource is actually needed/used.

retrieve-cp-data-soure

multimethod

Factory/lookup for real CP-datasource. String arg will be expected to be JNDI name of a javax.sql.ConnectionPoolDataSource. In this case the CP-datasource will be looked up in JNDI. If the arg is a map the class-typed :delegate-class will be used to create an instance and then all remaining keys/values will be used to set the instance’s Java-Bean properties.

spec->type

(spec->type spec)

Dispatch for retrieve-cp-data-soure. Returns type of spec (:jndi for String, :cp-class for maps).

-------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.xa-data-source.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.xa-data-source documentation

buttle.xa-data-source

The Buttle javax.sql.XADataSource.

4 |

This namespace delivers buttle.jdbc.XADataSource via :gen-class. This named class can be used as an XA-datasource class for application servers.

5 |

Example (Wildfly):

6 |
  <xa-datasource jndi-name="java:/jdbc/buttle-xa" pool-name="buttle-xa">
 7 |     <xa-datasource-class>buttle.jdbc.XADataSource</xa-datasource-class>
 8 |     <driver>buttle-driver</driver>
 9 |     <security>
10 |       <user-name>postgres-user</user-name>
11 |       <password>postgres-password</password>
12 |     </security>
13 |     <xa-datasource-property name="DelegateSpec">
14 |       {:delegate-class org.postgresql.xa.PGXADataSource
15 |        :url "jdbc:postgresql://127.0.0.1:6632/postgres"}
16 |     </xa-datasource-property>
17 |   </xa-datasource>
18 | 

-getLoginTimeout

(-getLoginTimeout this)

Implements javax.sql.CommonDataSource/getLoginTimeout. Just delegates to the referenced/internal XA-datasource (see -init).

-getLogWriter

(-getLogWriter this)

Implements javax.sql.CommonDataSource/getLogWriter. Just delegates to the referenced/internal XA-datasource (see -init).

-getParentLogger

(-getParentLogger this)

Implements javax.sql.CommonDataSource/getParentLogger. Just delegates to the referenced/internal XA-datasource (see -init).

-getXAConnection

(-getXAConnection this)(-getXAConnection this username password)

Implements javax.sql.XADataSource/getXAConnection. Just delegates to the referenced/internal XA-datasource (see -init).

-init

(-init)

Constructor function of buttle.jdbc.XADataSource. Calls make-xa-data-source and initialized state with that (i.e. the Buttle XA-datasource is cached).

-setDelegateSpec

(-setDelegateSpec this spec)

Implements buttle.xa_data_source.ButtleDataSource/setDelegateSpec. Just delegates to the referenced/internal Buttle XA-datasource (see -init).

-setLoginTimeout

(-setLoginTimeout this sec)

Implements javax.sql.CommonDataSource/setLoginTimeout. Just delegates to the referenced/internal XA-datasource (see -init).

-setLogWriter

(-setLogWriter this pr-wrt)

Implements javax.sql.CommonDataSource/setLogWriter. Just delegates to the referenced/internal XA-datasource (see -init).

make-xa-data-source

(make-xa-data-source)

Creates and returns a Buttle javax.sql.XADataSource.

19 |

Use setDelegateSpec to control what the real (or backing) javax.sql.XADataSource is. You can use String to use an XA-datasource from JNDI. Use a map to create an instance and set properties (see retrieve-xa-data-soure for details).

20 |

Note: creation is done on-demand when the backing XA-datasource is actually needed/used.

retrieve-xa-data-soure

multimethod

Factory/lookup for real XA-datasource. String arg will be expected to be JNDI name of a javax.sql.XADataSource. In this case the XA-datasource will be looked up in JNDI. If the arg is a map the class-typed :delegate-class will be used to create an instance and then all remaining keys/values will be used to set the instance’s Java-Bean properties.

spec->type

(spec->type spec)

Dispatch for retrieve-xa-data-soure. Returns type of spec (:jndi for String, :xa-class for maps).

-------------------------------------------------------------------------------- /resources/public/generated-doc/highlight/highlight.min.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | ;; re-enables http repository support in Leiningen 2.8 2 | (require 'cemerick.pomegranate.aether) 3 | (cemerick.pomegranate.aether/register-wagon-factory! 4 | "http" #(org.apache.maven.wagon.providers.http.HttpWagon.)) 5 | 6 | (defproject buttle/buttle "1.0.1-SNAPSHOT" 7 | 8 | :description "Buttle is a proxying JDBC driver with hooks." 9 | 10 | :url "https://github.com/henrik42/buttle/" 11 | 12 | :license {:name "Eclipse Public License" 13 | :url "http://www.eclipse.org/legal/epl-v10.html"} 14 | 15 | :dependencies [[org.clojure/clojure "1.9.0"] 16 | [org.clojure/core.async "0.4.490"]] 17 | 18 | ;; Produce the "named classes" buttle.jdbc.Driver and 19 | ;; buttle.jdbc.DataSource that you'll be using 20 | :aot [buttle.driver 21 | buttle.data-source 22 | buttle.xa-data-source 23 | buttle.connection-pool-data-source] 24 | 25 | ;; we need buttle.SetContextClassLoaderInStaticInitializer as a base 26 | ;; class for buttle.jdbc.Driver/driver.clj - so we'll compile Java 27 | ;; classes first and then Clojure files 28 | :java-source-paths ["java"] 29 | 30 | :target-path "target/%s" 31 | 32 | :plugins [[lein-swank "1.4.5"] 33 | [lein-codox "0.10.3"] 34 | [lein-marginalia "0.9.1"]] 35 | 36 | :aliases {"uberjar" ["do" "clean," "uberjar"] ;; builds driver/UBERJAR to target/uberjar/buttle-driver.jar 37 | "deploy" ["do" "clean," "deploy"] ;; deploys lib jar to snapshots/releases depending on version 38 | 39 | "deploy-driver" ["deploy-driver" ;; calls Buttle's plugin/leiningen/deploy_driver.clj 40 | ":leiningen/repository" ;; pseudo repository -- see plugin/leiningen/deploy_driver.clj 41 | "buttle/buttle" ;; group/artefact-id 42 | ":leiningen/version" ;; pseudo version number -- see plugin/leiningen/deploy_driver.clj 43 | "driver" ;; classifier 44 | "target/uberjar/buttle-driver.jar" ;; file -- see :uberjar-name 45 | ] 46 | 47 | "deploy-all" ["do" "deploy," "uberjar," "deploy-driver"] ;; depoy everything to snapshots/releases depending on version 48 | 49 | ;; -------------------------------------------------------- 50 | ;; RELEASE PROCEDURE 51 | ;; 52 | ;; A release requires invoking lein three times. See 53 | ;; comments on "release-broken!" below for some details 54 | ;; for why this is done this way. 55 | ;; 56 | ;; 1/3: lein with-profile +skip-test release-prepare! 57 | ;; or buttle_user= buttle_password= lein release-prepare! 58 | ;; 59 | ;; 2/3: lein with-profile +local release-deploy! 60 | ;; 61 | ;; 3/3: lein with-profile +local release-finish! 62 | ;; 63 | ;; After that you just need to push commits to github. 64 | ;; -------------------------------------------------------- 65 | 66 | "release-prepare!" ["do" 67 | ["vcs" "assert-committed"] 68 | ["test"] ;; skip via `with-profile +skip-test` 69 | 70 | ;; bump to release version --> next step! 71 | ["change" "version" "leiningen.release/bump-version" "release"]] 72 | 73 | "release-deploy!" ["do" 74 | ["make-doc"] 75 | ["vcs" "commit"] 76 | ["vcs" "tag" "--no-sign"] 77 | 78 | ;; build & deploy release version 79 | ["deploy-all"] ;; use +with-profile -- see :local profile 80 | 81 | ;; bump to next SNAPSHOT version --> next step! 82 | ["change" "version" "leiningen.release/bump-version"] 83 | ] 84 | 85 | "release-finish!" ["do" 86 | ["make-doc"] 87 | ["vcs" "commit"] 88 | 89 | ;; build & deploy new SNAPSHOT 90 | ["deploy-all"]] 91 | 92 | ;; ----------------------------------------------------------------------------------- 93 | ;; 94 | ;; ************ THIS IS BROKEN! ************ 95 | ;; 96 | ;; (kept just for future reference) 97 | ;; 98 | ;; The lein `do` task loads `project.clj` only ONCE. So 99 | ;; after ["change" "version" ,,,] the project version is 100 | ;; unchanged for the tasks which are invoked by `do`. The 101 | ;; `release` task loads `project.clj` before invoking each 102 | ;; of the `:release-tasks`. But the `release` task does 103 | ;; not honor `with-profile` "settings". So none of these 104 | ;; works. 105 | ;; 106 | ;; Workaround: split up the `do` tasks and invoke lein 107 | ;; from shell for each "release-step" (see above). Each 108 | ;; time lein is invoked `project.clj` will be read and so 109 | ;; the new/changed version info will be visible to the 110 | ;; invoked tasks. 111 | ;; 112 | ;; ----------------------------------------------------------------------------------- 113 | 114 | "release-broken!" 115 | ["do" 116 | 117 | ;; prepare / build and test 118 | ["vcs" "assert-committed"] 119 | ["test"] 120 | 121 | ;; bump to release version and commit 122 | ["change" "version" "leiningen.release/bump-version" "release"] 123 | ["make-doc"] 124 | ["vcs" "commit"] 125 | ["vcs" "tag" "--no-sign"] 126 | 127 | ;; build & deploy release version 128 | ["deploy-all"] 129 | 130 | ;; bump to next SNAPSHOT and commit 131 | ["change" "version" "leiningen.release/bump-version"] 132 | ["make-doc"] 133 | ["vcs" "commit"] 134 | ["vcs" "push"] 135 | 136 | ;; build & deploy SNASHOT version 137 | ["deploy-all"]] 138 | ;; -------- END BROKEN -------------------------------------- 139 | 140 | ;; make documenation which is kept in git repo 141 | "make-doc" ["with-profile" "+make-doc" "do" 142 | ["clean"] 143 | ["codox"] 144 | ["marg" 145 | "-d" "resources/public/generated-doc/" 146 | "-f" "buttle-source.html" 147 | "src"]]} 148 | 149 | :profiles {;; Build documentation from source. I check this into git 150 | ;; repo. Maybe thats not the best solution but this way I 151 | ;; see which things changed. There probably is a better 152 | ;; way. 153 | :make-doc {:codox {:metadata {:doc/format :markdown} 154 | :themes [:rdash] 155 | :doc-files ["README.md"] 156 | :output-path "resources/public/generated-doc/"} 157 | :dependencies [[codox-theme-rdash "0.1.2"]] 158 | :clean-targets ^{:protect false} ["resources/public/generated-doc"]} 159 | 160 | ;; If you want to deliver the Open Tracing und Jaeger 161 | ;; things with the UBERJAR you can do: 162 | ;; 163 | ;; lein with-profile +jaeger uberjar 164 | ;; 165 | ;; Note that if you're targeting an application server 166 | ;; you will probably have to use +wildfly profile as 167 | ;; well. 168 | :jaeger {:dependencies [[opentracing-clj "0.1.2"] 169 | [io.jaegertracing/jaeger-client "0.33.1"]]} 170 | 171 | ;; when building for Wildfly we must not include some 172 | ;; packages since these interfer with the classes 173 | ;; supplied by Wildfly. 174 | ;; 175 | ;; e.g. lein with-profile +jaeger,+wildfly uberjar 176 | :wildfly {:uberjar-exclusions [#"org/apache/commons/" 177 | #"org/apache/http/" 178 | #"javax/"]} 179 | 180 | ;; Just for dev 181 | :swank {:dependencies [[swank-clojure/swank-clojure "1.4.3"] 182 | [org.clojure/tools.nrepl "0.2.12"]]} 183 | 184 | :uberjar {:uberjar-name "buttle-driver.jar"} 185 | 186 | ;; use for `release-prepare!` 187 | :skip-test {:aliases {"test" ["do"]}} 188 | 189 | :test {:dependencies [[org.postgresql/postgresql "9.4.1212"] 190 | [opentracing-clj "0.1.2"] 191 | 192 | ;; https://github.com/h-thurow/Simple-JNDI/tree/master 193 | ;; https://github.com/hen/osjava/tree/master/simple-jndi 194 | [com.github.h-thurow/simple-jndi "0.17.2"] 195 | [io.jaegertracing/jaeger-client "0.33.1"] 196 | [org.slf4j/slf4j-jdk14 "1.7.25"]]} 197 | 198 | ;; run a local Nexus in a docker container with: 199 | ;; docker run -d -p 8081:8081 --name nexus sonatype/nexus:oss 200 | ;; 201 | ;; Then you can deploy a SNAPSHOT or release via 202 | ;; lein with-profile +local deploy-all 203 | :local {:repositories [["snapshots" {:url "http://localhost:8081/nexus/content/repositories/snapshots/" 204 | :sign-releases false :username "admin" :password "admin123"}] 205 | ["releases" {:url "http://localhost:8081/nexus/content/repositories/releases/" 206 | :sign-releases false :username "admin" :password "admin123"}]]}}) 207 | 208 | -------------------------------------------------------------------------------- /src/buttle/proxy.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.proxy 2 | "A proxy factory. 3 | 4 | This namespace delivers all functions needed to (generically) 5 | create proxies for JDBC related interfaces and to implement the 6 | delegation logic for these proxies that is needed to route method 7 | calls through to the _real_ JDBC driver's instances. 8 | 9 | In order to hook your own code into the delegation use `def-handle` 10 | to register your functions for certain method calls." 11 | 12 | (:require [buttle.event :as event] 13 | [buttle.util :as util])) 14 | 15 | (defn ->invoke-event 16 | "Returns an _invoke-event-map_." 17 | 18 | [the-method target-obj the-args] 19 | {:type :invoke 20 | :invoke (keyword 21 | (-> the-method .getDeclaringClass .getName str) 22 | (-> the-method .getName)) 23 | :args (into [] the-args) 24 | :thread (let [t (Thread/currentThread)] 25 | {:name (.getName t) 26 | :id (.getId t)}) 27 | :ts (System/currentTimeMillis)}) 28 | 29 | (defn ->throw-event 30 | "Returns a _throw-event-map_." 31 | 32 | [invoke-evt t] 33 | (let [now (System/currentTimeMillis)] 34 | {:type :throw 35 | :invoke invoke-evt 36 | :throw t 37 | :ts now 38 | :dur-msec (- now (:ts invoke-evt))})) 39 | 40 | (defn ->return-event 41 | "Returns a _return-event-map_." 42 | 43 | [invoke-evt r] 44 | (let [now (System/currentTimeMillis)] 45 | {:type :return 46 | :invoke invoke-evt 47 | :return r 48 | :ts (System/currentTimeMillis) 49 | :dur-msec (- now (:ts invoke-evt))})) 50 | 51 | (def function-default-hierarchy 52 | "Atom carrying a simple/shallow hierarchy of `:buttle/` 53 | to `:buttle/default` entries. This hierarchy is used for `handle` 54 | multi-method and `fix-prefers!`." 55 | 56 | (atom (make-hierarchy))) 57 | 58 | (defn invoke-fn 59 | "Invocation handler for `make-proxy`. It delegates any method 60 | invocation of `proxy-type` (which may be an interface or a vector 61 | of interfaces) to `handler-fn`. Any other method invocations (like 62 | `Object.toString()`) will be invoked on `target-object`. 63 | 64 | Note that any 65 | `java.lang.reflect.InvocationTargetException` (incl. those coming 66 | from `handler-fn`) will be un-rolled so that the cause `Exception` 67 | will come out of `invoke-fn` and thus the proxy made by 68 | `make-proxy`. 69 | 70 | This function is not meant for public usage. It functions as a 71 | hookable delegation-point for `make-proxy` so that you may 72 | re-bind/re-def the var when debugging and hacking." 73 | 74 | [proxy-type target-obj handler-fn the-proxy the-method the-args] 75 | (try 76 | (if ((if (vector? proxy-type) (into #{} proxy-type) #{proxy-type}) 77 | (.getDeclaringClass the-method)) 78 | (handler-fn the-method target-obj the-args) 79 | (.invoke the-method target-obj the-args)) 80 | (catch java.lang.reflect.InvocationTargetException t 81 | (throw (.getCause t))))) 82 | 83 | (defn make-proxy 84 | "A proxy factory. 85 | 86 | Creates and returns a __Java dynamic proxy__ with 87 | `proxy-type` (which may be an interface or a vector of 88 | interfaces). 89 | 90 | The proxy delegates any method invocation to `invoke-fn` which in 91 | turn delegates to `handler-fn`. 92 | 93 | The classloader for this proxy is taken from the first 94 | interface. If you want to delegate invocations on this proxy D 95 | through handler-fn and onto a Clojure proxy P you have to make 96 | sure, that D's and P's method/class declarations are 97 | compatible. See notes on classloading in 98 | src/buttle/connection_pool_data_source.clj for more details. 99 | 100 | Example usage: 101 | 102 | (make-proxy java.sql.Driver \"foo-driver\" 103 | (fn [the-method target-obj the-args] 104 | (condp = (.getName the-method) 105 | \"acceptsURL\" true 106 | \"connect\" (proxy [java.sql.Connection] [] 107 | (toString [] \"foo-connection\")))))" 108 | 109 | [proxy-type target-obj handler-fn] 110 | (let [pt (if (vector? proxy-type) 111 | (into-array proxy-type) 112 | (into-array [proxy-type]))] 113 | (java.lang.reflect.Proxy/newProxyInstance 114 | (.getClassLoader (first pt)) 115 | pt 116 | (proxy [java.lang.reflect.InvocationHandler] [] 117 | (invoke [the-proxy the-method the-args] 118 | (invoke-fn proxy-type target-obj handler-fn the-proxy the-method the-args)))))) 119 | 120 | (defn invocation-key 121 | "Dispatch function for `handle`. Returns a vector with the method's 122 | declaring class and `buttle/` namespaced keyword for the method's 123 | name. 124 | 125 | Example: 126 | 127 | (-> java.sql.Connection 128 | (.getMethod \"close\" nil) 129 | invocation-key) 130 | ;; --> [java.sql.Connection :buttle/close]" 131 | 132 | [the-method & _] 133 | [(-> the-method .getDeclaringClass) 134 | (->> the-method .getName (keyword "buttle"))]) 135 | 136 | (defmulti handle 137 | "A generic delegation function (arity `[the-method target-obj 138 | the-args]`) which delivers proxy'ed return values. 139 | 140 | The `:default` implementation just delegates to `handle-default`. 141 | 142 | The multi-method dispatch is done on `invocation-key`. 143 | 144 | This is a multi-method so that you have a means (see `def-handle`) 145 | to hook into the execution/delegation/proxying logic for some/any of 146 | the proxy'ed interface types i.e. `invocation-key` dispatch values." 147 | 148 | #'invocation-key :hierarchy function-default-hierarchy) 149 | 150 | (defn handle-default 151 | "Calls `the-method` on `target-obj` with `the-args`, creates a proxy 152 | via `make-proxy` (which uses `handle` as its `handler-fn`) for 153 | non-`nil` interface-typed return values and returns the (possibly 154 | proxy'ed) result. Throws if the invoked method throws. 155 | 156 | Sends (`buttle.event/send-event`) an `->invoke-event` before calling 157 | `the-method`. Sends a `->throw-event` if `the-method` call 158 | throws. Else sends a `->return-event` before result proxy is 159 | created." 160 | 161 | [the-method target-obj the-args] 162 | 163 | (let [invoke-evt (->invoke-event the-method target-obj the-args) 164 | _ (event/send-event invoke-evt) 165 | r (try 166 | (.invoke the-method target-obj the-args) 167 | (catch Throwable t 168 | (event/send-event (->throw-event invoke-evt t)) 169 | (throw t))) 170 | _ (event/send-event (->return-event invoke-evt r)) 171 | rt (and r (.getReturnType the-method))] 172 | (if (and rt (.isInterface rt)) 173 | ;; see comment for buttle.driver/make-driver -- need 174 | ;; Class/forName because when using class literal 175 | ;; buttle.jdbc.Driver the compiler complains about a cyclic 176 | ;; dependency driver/proxy/driver. 177 | (make-proxy rt r 178 | (fn [the-method target-obj the-args] 179 | (util/with-tccl (.getClassLoader 180 | (Class/forName "buttle.jdbc.Driver")) 181 | (handle the-method target-obj the-args)))) 182 | r))) 183 | 184 | (defmethod handle :default [the-method target-obj the-args] 185 | (handle-default the-method target-obj the-args)) 186 | 187 | (defmacro def-handle 188 | "Registers a `handle` method implementation for dispatch (as of 189 | `invocation-key`) value `[clss mthd]`. Can be undone via 190 | `remove-handle`. Re-registering just overwrites. 191 | 192 | Uses `fix-prefers!` on the given key. So you may use keys like 193 | __(a)__ `[Object :buttle/getCatalog]` and __(b)__ 194 | `[java.sql.Connection :buttle/default]` to register an 195 | implementation for __(a)__ specific method __names__ (ignoring the 196 | defining class/interface) and __(b)__ specific interfaces ignoring 197 | the method name (with a __preference__ for __(b)__ in conflicting 198 | cases). 199 | 200 | The most specific registration would be `[java.sql.Connection 201 | :buttle/getCatalog]`. So this would be prefered over __(a)__ and 202 | __(b)__ when present. 203 | 204 | __Note:__ This macro (i.e. the resulting code) may not be not 205 | thread-safe because it uses `fix-prefers!` which may not be 206 | thread-safe. You should use `def-handle` only in top-level-forms for 207 | defining `handle` method-implemenations but not in functions you 208 | call as part of the program flow." 209 | 210 | [[clss mthd] [the-method target-obj the-args] body] 211 | (list 'do 212 | (list 'buttle.proxy/fix-prefers! [clss mthd]) 213 | (list 'defmethod 'buttle.proxy/handle [clss mthd] '[the-method target-obj the-args] 214 | body))) 215 | 216 | (defn remove-handle 217 | "Removes the `handle` for `[clss mthd]`. No-op if there is no 218 | registered `handle` for this key." 219 | 220 | [[clss mthd]] 221 | (remove-method handle [clss mthd])) 222 | 223 | (defn methods-of 224 | "Returns seq of `:buttle/` namespaced method names of class `clss`." 225 | 226 | [clss] 227 | (->> clss 228 | .getMethods 229 | (map #(keyword "buttle" (.getName %))))) 230 | 231 | (defn fix-prefers! 232 | "If `(= mthd :buttle/default)` makes all/any method `m` of class 233 | `clss` (via `derive`) a child of `:buttle/default` and _prefers_ 234 | `[clss :buttle/default]` over `[java.lang.Object m]`. This lets you 235 | dispatch via `invocation-key` with an _inheritance_ mechanism which 236 | uses/combines `isa?` on types `(isa? Connection Object)` and on 237 | method keys `(isa? :buttle/getCatalog :buttle/default)`. 238 | 239 | Now you can __(a)__ `def-handle [Object :buttle/getCatalog]` and 240 | __(b)__ `def-handle [java.sql.Connection :buttle/default]` with a 241 | __preference__ for __(a)__ when calling `Connection/getCatalog`. 242 | 243 | This function is thread-safe only if `derive` and `prefer-method` 244 | are so. You will usually not use this function directly but only 245 | through `def-handle`." 246 | 247 | [[clss mthd]] 248 | (when (= mthd :buttle/default) 249 | (when (= Object clss) 250 | (throw (RuntimeException. "You cannot use def-handle with Object/:buttle/default"))) 251 | (doseq [m (methods-of clss)] 252 | ;; MUTATION/SIDEEFFECT!!!! 253 | (swap! function-default-hierarchy derive m :buttle/default)) 254 | (doseq [m (descendants @function-default-hierarchy :buttle/default)] 255 | ;; MUTATION/SIDEEFFECT!!!! 256 | (prefer-method handle 257 | [clss :buttle/default] 258 | [java.lang.Object m])))) 259 | 260 | -------------------------------------------------------------------------------- /resources/public/generated-doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | Buttle 1.0.1-SNAPSHOT

Buttle 1.0.1-SNAPSHOT

Released under the Eclipse Public License

Buttle is a proxying JDBC driver with hooks.

Installation

To install, add the following dependency to your project or build file:

[buttle "1.0.1-SNAPSHOT"]

Topics

Namespaces

buttle.driver-manager

A thin/simple Clojure API around java.sql.DriverManager.

buttle.event

Send events to consumers via clojure.core.async/chan.

Public variables and functions:

buttle.util

Just some helpers.

Public variables and functions:

-------------------------------------------------------------------------------- /resources/public/generated-doc/css/default.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=PT+Sans'); 2 | 3 | body { 4 | font-family: 'PT Sans', Helvetica, sans-serif; 5 | font-size: 14px; 6 | } 7 | 8 | a { 9 | color: #337ab7; 10 | text-decoration: none; 11 | } 12 | 13 | a:hover { 14 | color: #30426a; 15 | text-decoration: underline; 16 | } 17 | 18 | pre, code { 19 | font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; 20 | font-size: 9pt; 21 | margin: 15px 0; 22 | } 23 | 24 | h1 { 25 | font-weight: normal; 26 | font-size: 29px; 27 | margin: 10px 0 2px 0; 28 | padding: 0; 29 | } 30 | 31 | h2 { 32 | font-weight: normal; 33 | font-size: 25px; 34 | } 35 | 36 | h3 > a:hover { 37 | text-decoration: none; 38 | } 39 | 40 | .document h1, .namespace-index h1 { 41 | font-size: 32px; 42 | margin-top: 12px; 43 | } 44 | 45 | #header, #content, .sidebar { 46 | position: fixed; 47 | } 48 | 49 | #header { 50 | top: 0; 51 | left: 0; 52 | right: 0; 53 | height: 22px; 54 | color: #f5f5f5; 55 | padding: 5px 7px; 56 | } 57 | 58 | #content { 59 | top: 32px; 60 | right: 0; 61 | bottom: 0; 62 | overflow: auto; 63 | background: #fff; 64 | color: #333; 65 | padding: 0 18px; 66 | } 67 | 68 | .sidebar { 69 | position: fixed; 70 | top: 32px; 71 | bottom: 0; 72 | overflow: auto; 73 | } 74 | 75 | .sidebar.primary { 76 | background: #30426a; 77 | border-right: solid 1px #cccccc; 78 | left: 0; 79 | width: 250px; 80 | color: white; 81 | font-size: 110%; 82 | } 83 | 84 | .sidebar.secondary { 85 | background: #f2f2f2; 86 | border-right: solid 1px #d7d7d7; 87 | left: 251px; 88 | width: 200px; 89 | font-size: 110%; 90 | } 91 | 92 | #content.namespace-index, #content.document { 93 | left: 251px; 94 | } 95 | 96 | #content.namespace-docs { 97 | left: 452px; 98 | } 99 | 100 | #content.document { 101 | padding-bottom: 10%; 102 | } 103 | 104 | #header { 105 | background: #2d3e63; 106 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); 107 | z-index: 100; 108 | } 109 | 110 | #header h1 { 111 | margin: 0; 112 | padding: 0; 113 | font-size: 18px; 114 | font-weight: lighter; 115 | text-shadow: -1px -1px 0px #333; 116 | } 117 | 118 | #header h1 .project-version { 119 | font-weight: normal; 120 | } 121 | 122 | .project-version { 123 | padding-left: 0.15em; 124 | } 125 | 126 | #header a, .sidebar a { 127 | display: block; 128 | text-decoration: none; 129 | } 130 | 131 | #header a { 132 | color: #f5f5f5; 133 | } 134 | 135 | .sidebar.primary, .sidebar.primary a { 136 | color: #b2bfdc; 137 | } 138 | 139 | .sidebar.primary a:hover { 140 | color: white; 141 | } 142 | 143 | .sidebar.secondary, .sidebar.secondary a { 144 | color: #738bc0; 145 | } 146 | 147 | .sidebar.secondary a:hover { 148 | color: #2d3e63; 149 | } 150 | 151 | #header h2 { 152 | float: right; 153 | font-size: 9pt; 154 | font-weight: normal; 155 | margin: 4px 3px; 156 | padding: 0; 157 | color: #bbb; 158 | } 159 | 160 | #header h2 a { 161 | display: inline; 162 | } 163 | 164 | .sidebar h3 { 165 | margin: 0; 166 | padding: 10px 13px 0 13px; 167 | font-size: 19px; 168 | font-weight: lighter; 169 | } 170 | 171 | .sidebar.primary h3.no-link { 172 | text-transform: uppercase; 173 | font-size: 12px; 174 | color: #738bc0; 175 | } 176 | 177 | .sidebar.secondary h3 a { 178 | text-transform: uppercase; 179 | font-size: 12px; 180 | color: #2d3e63; 181 | } 182 | 183 | .sidebar ul { 184 | padding: 7px 0 6px 0; 185 | margin: 0; 186 | } 187 | 188 | .sidebar ul.index-link { 189 | padding-bottom: 4px; 190 | } 191 | 192 | .sidebar li { 193 | display: block; 194 | vertical-align: middle; 195 | } 196 | 197 | .sidebar li a, .sidebar li .no-link { 198 | border-left: 3px solid transparent; 199 | padding: 0 10px; 200 | white-space: nowrap; 201 | } 202 | 203 | .sidebar li .inner { 204 | display: inline-block; 205 | padding-top: 7px; 206 | height: 24px; 207 | } 208 | 209 | .sidebar li a, .sidebar li .tree { 210 | height: 31px; 211 | } 212 | 213 | .depth-1 .inner { padding-left: 2px; } 214 | .depth-2 .inner { padding-left: 6px; } 215 | .depth-3 .inner { padding-left: 20px; } 216 | .depth-4 .inner { padding-left: 34px; } 217 | .depth-5 .inner { padding-left: 48px; } 218 | .depth-6 .inner { padding-left: 62px; } 219 | 220 | .sidebar li .tree { 221 | display: block; 222 | float: left; 223 | position: relative; 224 | top: -10px; 225 | margin: 0 4px 0 0; 226 | padding: 0; 227 | } 228 | 229 | .sidebar li.depth-1 .tree { 230 | display: none; 231 | } 232 | 233 | .sidebar li .tree .top, .sidebar li .tree .bottom { 234 | display: block; 235 | margin: 0; 236 | padding: 0; 237 | width: 7px; 238 | } 239 | 240 | .sidebar li .tree .top { 241 | border-left: 1px solid #aaa; 242 | border-bottom: 1px solid #aaa; 243 | height: 19px; 244 | } 245 | 246 | .sidebar li .tree .bottom { 247 | height: 22px; 248 | } 249 | 250 | .sidebar li.branch .tree .bottom { 251 | border-left: 1px solid #aaa; 252 | } 253 | 254 | .sidebar.primary li.current a { 255 | border-left: 3px solid #e99d1a; 256 | color: white; 257 | } 258 | 259 | .sidebar.secondary li.current a { 260 | border-left: 3px solid #2d3e63; 261 | color: #33a; 262 | } 263 | 264 | .namespace-index h2 { 265 | margin: 30px 0 0 0; 266 | } 267 | 268 | .namespace-index h3 { 269 | font-size: 16px; 270 | font-weight: bold; 271 | margin-bottom: 0; 272 | letter-spacing: 0.05em; 273 | border-bottom: solid 1px #ddd; 274 | max-width: 680px; 275 | background-color: #fafafa; 276 | padding: 0.5em; 277 | } 278 | 279 | .namespace-index .topics { 280 | padding-left: 30px; 281 | margin: 11px 0 0 0; 282 | } 283 | 284 | .namespace-index .topics li { 285 | padding: 5px 0; 286 | } 287 | 288 | .namespace-docs h3 { 289 | font-size: 18px; 290 | font-weight: bold; 291 | } 292 | 293 | .public h3 { 294 | margin: 0; 295 | float: left; 296 | } 297 | 298 | .usage { 299 | clear: both; 300 | } 301 | 302 | .public { 303 | margin: 0; 304 | border-top: 1px solid #e0e0e0; 305 | padding-top: 14px; 306 | padding-bottom: 6px; 307 | } 308 | 309 | .public:last-child { 310 | margin-bottom: 20%; 311 | } 312 | 313 | .members .public:last-child { 314 | margin-bottom: 0; 315 | } 316 | 317 | .members { 318 | margin: 15px 0; 319 | } 320 | 321 | .members h4 { 322 | color: #555; 323 | font-weight: normal; 324 | font-variant: small-caps; 325 | margin: 0 0 5px 0; 326 | } 327 | 328 | .members .inner { 329 | padding-top: 5px; 330 | padding-left: 12px; 331 | margin-top: 2px; 332 | margin-left: 7px; 333 | border-left: 1px solid #bbb; 334 | } 335 | 336 | #content .members .inner h3 { 337 | font-size: 12pt; 338 | } 339 | 340 | .members .public { 341 | border-top: none; 342 | margin-top: 0; 343 | padding-top: 6px; 344 | padding-bottom: 0; 345 | } 346 | 347 | .members .public:first-child { 348 | padding-top: 0; 349 | } 350 | 351 | h4.type, 352 | h4.dynamic, 353 | h4.added, 354 | h4.deprecated { 355 | float: left; 356 | margin: 3px 10px 15px 0; 357 | font-size: 15px; 358 | font-weight: bold; 359 | font-variant: small-caps; 360 | } 361 | 362 | .public h4.type, 363 | .public h4.dynamic, 364 | .public h4.added, 365 | .public h4.deprecated { 366 | font-size: 13px; 367 | font-weight: bold; 368 | margin: 3px 0 0 10px; 369 | } 370 | 371 | .members h4.type, 372 | .members h4.added, 373 | .members h4.deprecated { 374 | margin-top: 1px; 375 | } 376 | 377 | h4.type { 378 | color: #717171; 379 | } 380 | 381 | h4.dynamic { 382 | color: #9933aa; 383 | } 384 | 385 | h4.added { 386 | color: #508820; 387 | } 388 | 389 | h4.deprecated { 390 | color: #880000; 391 | } 392 | 393 | .namespace { 394 | margin-bottom: 30px; 395 | } 396 | 397 | .namespace:last-child { 398 | margin-bottom: 10%; 399 | } 400 | 401 | .index { 402 | padding: 0; 403 | font-size: 80%; 404 | margin: 15px 0; 405 | line-height: 1.6em; 406 | } 407 | 408 | .index * { 409 | display: inline; 410 | } 411 | 412 | .index p { 413 | padding-right: 3px; 414 | } 415 | 416 | .index li { 417 | padding-right: 5px; 418 | } 419 | 420 | .index ul { 421 | padding-left: 0; 422 | } 423 | 424 | .type-sig { 425 | clear: both; 426 | color: #088; 427 | } 428 | 429 | .type-sig pre { 430 | padding-top: 10px; 431 | margin: 0; 432 | } 433 | 434 | .usage code { 435 | display: block; 436 | color: #008; 437 | margin: 2px 0; 438 | } 439 | 440 | .usage code:first-child { 441 | padding-top: 10px; 442 | } 443 | 444 | p { 445 | margin: 15px 0; 446 | } 447 | 448 | .public p:first-child, .public pre.plaintext { 449 | margin-top: 12px; 450 | } 451 | 452 | .doc { 453 | margin: 0 0 26px 0; 454 | clear: both; 455 | } 456 | 457 | .public .doc { 458 | margin: 0; 459 | } 460 | 461 | .namespace-index { 462 | font-size: 120%; 463 | } 464 | 465 | .namespace-index .doc { 466 | margin-bottom: 20px; 467 | } 468 | 469 | .namespace-index .namespace .doc { 470 | margin-bottom: 10px; 471 | } 472 | 473 | .markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { 474 | line-height: 1.6em; 475 | } 476 | 477 | .markdown h2 { 478 | font-weight: normal; 479 | font-size: 25px; 480 | } 481 | 482 | #content .markdown h3 { 483 | font-size: 20px; 484 | } 485 | 486 | .markdown h4 { 487 | font-size: 15px; 488 | } 489 | 490 | .doc, .public, .namespace .index { 491 | max-width: 680px; 492 | overflow-x: visible; 493 | } 494 | 495 | .markdown pre > code { 496 | display: block; 497 | padding: 10px; 498 | } 499 | 500 | .markdown pre > code, .src-link a { 501 | border: 1px solid #e4e4e4; 502 | border-radius: 2px; 503 | } 504 | 505 | .src-link a { 506 | background: #f6f6f6; 507 | } 508 | 509 | .markdown code:not(.hljs) { 510 | color: #c7254e; 511 | background-color: #f9f2f4; 512 | border-radius: 4px; 513 | font-size: 90%; 514 | padding: 2px 4px; 515 | } 516 | 517 | pre.deps { 518 | display: inline-block; 519 | margin: 0 10px; 520 | border: 1px solid #e4e4e4; 521 | border-radius: 2px; 522 | padding: 10px; 523 | background-color: #f6f6f6; 524 | } 525 | 526 | .markdown hr { 527 | border-style: solid; 528 | border-top: none; 529 | color: #ccc; 530 | } 531 | 532 | .doc ul, .doc ol { 533 | padding-left: 30px; 534 | } 535 | 536 | .doc table { 537 | border-collapse: collapse; 538 | margin: 0 10px; 539 | } 540 | 541 | .doc table td, .doc table th { 542 | border: 1px solid #dddddd; 543 | padding: 4px 6px; 544 | } 545 | 546 | .doc table th { 547 | background: #f2f2f2; 548 | } 549 | 550 | .doc dl { 551 | margin: 0 10px 20px 10px; 552 | } 553 | 554 | .doc dl dt { 555 | font-weight: bold; 556 | margin: 0; 557 | padding: 3px 0; 558 | border-bottom: 1px solid #ddd; 559 | } 560 | 561 | .doc dl dd { 562 | padding: 5px 0; 563 | margin: 0 0 5px 10px; 564 | } 565 | 566 | .doc abbr { 567 | border-bottom: 1px dotted #333; 568 | font-variant: none; 569 | cursor: help; 570 | } 571 | 572 | .src-link { 573 | margin-bottom: 15px; 574 | } 575 | 576 | .src-link a { 577 | font-size: 70%; 578 | padding: 1px 4px; 579 | text-decoration: none; 580 | color: #5555bb; 581 | background-color: #f6f6f6; 582 | } 583 | 584 | blockquote { 585 | opacity: 0.6; 586 | border-left: solid 2px #ddd; 587 | margin-left: 0; 588 | padding-left: 1em; 589 | } 590 | 591 | /* Responsiveness Theme */ 592 | 593 | @media (max-device-width: 480px) { 594 | .sidebar { 595 | display:none; 596 | } 597 | 598 | #content { 599 | position: relative; 600 | left: initial !important; 601 | top: 110px; 602 | padding: 0 1em; 603 | } 604 | 605 | #header { 606 | display: flex; 607 | flex-direction: column-reverse; 608 | height: 100px; 609 | } 610 | 611 | #header > h1 { 612 | font-size: 52px; 613 | } 614 | 615 | #header h2 { 616 | float: none; 617 | font-size: 20px; 618 | } 619 | 620 | .namespace-index > h1 { 621 | display: none; 622 | } 623 | 624 | .public, .doc, .namespace > .index, .namespace > .doc, .namespace > h3 { 625 | max-width: initial; 626 | } 627 | 628 | .doc { 629 | text-align: justify; 630 | } 631 | 632 | .public { 633 | padding-top: 2em; 634 | padding-bottom: 2em; 635 | } 636 | 637 | .public > h3 { 638 | font-size: 300%; 639 | } 640 | 641 | .public > h4.type, .public > h4.added, .public > h4.deprecated { 642 | font-size: 150%; 643 | margin-top: 1em; 644 | } 645 | 646 | pre > code { 647 | font-size: 200%; 648 | } 649 | } 650 | -------------------------------------------------------------------------------- /src/buttle/driver.clj: -------------------------------------------------------------------------------- 1 | (ns buttle.driver 2 | "The _Buttle_ `java.sql.Driver`. 3 | 4 | This namespace delivers `buttle.jdbc.Driver` via `:gen-class`. This 5 | named class can be used by tools (like SQuirreL) and the SPI 6 | `services/java.sql.Driver`. 7 | 8 | The `-init` constructor function will register a _Buttle_ `Driver` 9 | proxy (see `make-driver`) with the `java.sql.DriverManager`. So 10 | whenever an instance of `buttle.jdbc.Driver` is created, a new 11 | proxy ( __not__ the `buttle.jdbc.Driver`!) is registered. 12 | 13 | __Example JDBC-URL__: 14 | 15 | jdbc:buttle:{:user \"\" :password \"\" :target-url \"jdbc:postgresql://127.0.0.1:6632/postgres\"} 16 | 17 | __Example Wildfly datasource__ (see README for more details): 18 | 19 | 20 | buttle-driver 21 | 22 | jdbc:buttle:{ 23 | :user \"\" 24 | :password \"\" 25 | :class-for-name \"org.postgresql.Driver\" 26 | :target-url \"jdbc:postgresql://:/\"} 27 | 28 | 29 | 30 | When this namespace is loaded `eval-buttle-user-file!` will be 31 | executed. 32 | 33 | __WARNING:__ this means that anyone who controls the system 34 | properties of the hosting JVM can run any Clojure code (but then 35 | again --- when someone controls the system properties he/she is 36 | probably able to run any command anyway). 37 | 38 | Functions in this namespace deliver all the functionality needed for 39 | the `java.sql.Driver` interface/contract. Things for connections, 40 | statements etc. are all delivered through `buttle.proxy`." 41 | 42 | (:gen-class 43 | :init init 44 | :state state 45 | :name buttle.jdbc.Driver 46 | :extends buttle.SetContextClassLoaderInStaticInitializer 47 | :implements [java.sql.Driver]) 48 | (:require [buttle.driver-manager :as mgr] 49 | [buttle.util :as util] 50 | [buttle.proxy :as proxy] 51 | [buttle.data-source :as buttle-ds])) 52 | 53 | (defn parse-jdbc-url 54 | "Parses a _Buttle_ JDBC URL. 55 | 56 | A _Buttle_ JDBC URL has the format `#\"jdbc:buttle:(.+)\"`. Any 57 | text after `jdbc:buttle:` will be `read-string-eval`'ed and should 58 | yield a map with keys `:target-url`, `:user` and `:password`. The 59 | `eval`'ed value is returned (even if not a map). If `url` does not 60 | match the pattern `nil` is returned. If `read-string-eval` throws 61 | an `Exception` then an `Exception` will be thrown." 62 | 63 | [url] 64 | (try 65 | (some-> (re-matches #"jdbc:buttle:(.+)" (.replaceAll url "[\\n\\t\\r]+" " ")) 66 | second 67 | read-string 68 | eval) 69 | (catch Throwable t 70 | (throw (ex-info "Could not parse URL" {:url url} t))))) 71 | 72 | #_ 73 | (let [f {"foo" "FOO" "bar" "BAR"} 74 | {foo "foo" :as g} f] 75 | [g foo]) 76 | 77 | (defn accepts-url-fn 78 | "Parses `url` via `parse-jdbc-url` and retrieves the keys 79 | `:target-url`, `:user`, `:password`, `:class-for-name` and 80 | `:datasource-spec` from the returned value (assuming that it is a 81 | map). Returns a map with those keys/values." 82 | 83 | [url] 84 | (when-let [{:keys [target-url user password class-for-name datasource-spec]} (parse-jdbc-url url)] 85 | {:target-url target-url 86 | :user user 87 | :password password 88 | :class-for-name class-for-name 89 | :datasource-spec datasource-spec})) 90 | 91 | (defn connect-fn 92 | "Returns a JDBC connection for given `url`. 93 | 94 | Returns `nil` if `url` is not a _Buttle_ URL (as of 95 | `accepts-url-fn`). Else opens a JDBC `Connection` to `:target-url` 96 | via `buttle.driver-manager/get-connection`. If that throws then this 97 | function throws. Otherwise the connection is returned. Note that in 98 | this case _Buttle_ does not call/use the proxied JDBC driver 99 | directly (but relies on the `DriverManager`). 100 | 101 | If `:datasource-spec` is given in the _Buttle_ URL a datasource 102 | `ds` (as of `buttle-ds/retrieve-data-soure`) will be used instead of 103 | the `buttle.driver-manager/get-connection`. 104 | 105 | Authentication credentials (user and password) are taken from 106 | `info` (keys `\"user\"` and `\"password\"`) if given. Else they are 107 | taken from _Buttle_ URL (keys `:user` and `:password`). If the 108 | connection is opened via `DriverManager` then `info` will be passed 109 | onto `DriverManager/getConnection` so that any properties beyond 110 | `\"user\"` and `\"password\"` (which are set to the authentication 111 | credentials) contained in `info` are preserved. 112 | 113 | If `:class-for-name` is set in the _Buttle_ `url` then 114 | calls `(Class/forName class-for-name)` before opening the 115 | connection. This takes care of cases when the proxied driver is not 116 | loaded auto-magically by the `DriverManager`. This may happen when 117 | the `DriverManager` is initialized and the classloader does not 118 | _see_ the proxied driver classes at that point (e.g. in JEE 119 | application servers when loading things via isolated classloaders)." 120 | 121 | [url {info-user "user" info-password "password" :as info}] 122 | (when-let [{:keys [target-url user password class-for-name datasource-spec] :as args} (accepts-url-fn url)] 123 | (try 124 | (when class-for-name 125 | (Class/forName class-for-name)) 126 | ;; Take user/pw from info if given. Else use user/pw from 127 | ;; url. If delegating to DriverManager pass info with 128 | ;; user/pw. We do this since there may be more things in info 129 | ;; and we want Buttle to be as "transarent" as we can make it. 130 | (let [[user password] (if (and (empty? info-user) (empty? info-password)) 131 | [user password] 132 | [info-user info-password])] 133 | (if datasource-spec 134 | (let [ds (buttle-ds/retrieve-data-soure datasource-spec)] 135 | (if-not password 136 | (.getConnection ds) 137 | (.getConnection ds user password))) 138 | (mgr/get-connection target-url (let [info (if info (.clone info) 139 | (java.util.Properties.))] 140 | (when user (.setProperty info "user" user)) 141 | (when password (.setProperty info "password" password)) 142 | info)))) 143 | (catch Throwable t 144 | (throw 145 | (RuntimeException. 146 | (format "Could not connect to %s %s %s: %s" url info args t) t)))))) 147 | 148 | (defn make-driver 149 | "Creates and returns a _Buttle_ `java.sql.Driver`. 150 | 151 | Note that the underlying driver (which delegates to the real 152 | driver) is a Clojure `proxy` (not an instance of 153 | `buttle.jdbc.Driver`) which is wrapped by a 154 | `buttle.proxy/make-proxy`. So calls to retured driver can be 155 | intercepted by `buttle.proxy/def-handle`. 156 | 157 | This driver can be registered with the 158 | `java.sql.DriverManager`. There are two important methods that this 159 | driver (proxy) implements: `connect` and `acceptsURL`. These are 160 | needed for interaction with the `DriverManager` so that the 161 | _Buttle_ driver can be _picked up_ for _Buttle_ URLs. 162 | 163 | Note: the _Buttle_ proxy will set the current thread's context 164 | classloader (TCCL) to _Buttle_'s classloader when delegating to 165 | `buttle.proxy/handle`. This is needed for cases when _Buttle_ is 166 | used as a data-source and deployed as a _module_ in Wildfly/JBoss 167 | application server. In this case `clojure.lang.RT` tries to load 168 | `clojure/core.clj` which it can't because it uses the current 169 | thread's context classloader and for Wildfly that will not be the 170 | _Buttle_ module's classloader. So we explicitly set the TCCL and 171 | things work. In non-application-server cases this usually does not 172 | hurt. At the moment you cannot configure this feature." 173 | 174 | [] 175 | (proxy/make-proxy 176 | java.sql.Driver 177 | (proxy [java.sql.Driver] [] 178 | ;; java.sql.Driver.connect(String url, Properties info) 179 | ;; 180 | ;; When using a Wildfly the container will pass-in 181 | ;; settings in `info` with String-typed "user" and 182 | ;; "password" keyed values. In this case _Buttle_ has to decide 183 | ;; which credentials to use. At the moment we just ignore `info`. 184 | (connect [url info] 185 | (connect-fn url info)) 186 | ;; boolean acceptsURL(String url) 187 | (acceptsURL [url] 188 | (boolean (accepts-url-fn url))) 189 | ;; DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) 190 | (getPropertyInfo [url info] 191 | (make-array java.sql.DriverPropertyInfo 0)) 192 | ;; int getMajorVersion(); 193 | (getMajorVersion [] 194 | 47) 195 | ;; int getMinorVersion(); 196 | (getMinorVersion [] 197 | 11) 198 | ;; boolean jdbcCompliant(); 199 | (jdbcCompliant [] 200 | true) 201 | ;; Logger getParentLogger() 202 | (getParentLogger [] 203 | (java.util.logging.Logger/getLogger "buttle"))) 204 | (fn [the-method target-obj the-args] 205 | (util/with-tccl (.getClassLoader buttle.jdbc.Driver) 206 | (proxy/handle the-method target-obj the-args))))) 207 | 208 | (def -init 209 | "Constructor function of `buttle.jdbc.Driver`. 210 | 211 | On first invokation creates a _Buttle_ `Driver` (see 212 | `make-driver`), keeps a reference to it and registers it with the 213 | `java.sql.DriverManager`. 214 | 215 | This _Buttle_ driver becomes the internal `state` of the 216 | `buttle.jdbc.Driver`. All `Driver` method implemenations of this 217 | class delegate to this internal driver." 218 | 219 | (let [driver (atom nil)] 220 | (fn [] 221 | (if-let [r @driver] 222 | [[] r] 223 | (let [r (make-driver)] 224 | (mgr/register-driver r) 225 | (reset! driver r) 226 | [[] r]))))) 227 | 228 | (defn -connect 229 | "Implements `java.sql.Driver.connect(String, Properties)`. Just 230 | delegates to the referenced/internal driver (see `-init`) which 231 | calls `connect-fn`." 232 | 233 | [this url info] 234 | (.connect (.state this) url info)) 235 | 236 | (defn -acceptsURL 237 | "Implements `java.sql.Driver.acceptsURL(String)`. Just delegates to 238 | the referenced/internal driver (see `-init`)." 239 | 240 | [this url] 241 | (.acceptsURL (.state this) url)) 242 | 243 | (defn -getPropertyInfo 244 | "Just delegates to the referenced/internal driver (see `-init`)." 245 | 246 | [this url info] 247 | (.getPropertyInfo (.state this) url info)) 248 | 249 | (defn -getMajorVersion 250 | "Just delegates to the referenced/internal driver (see `-init`)." 251 | 252 | [this] 253 | (.getMajorVersion (.state this))) 254 | 255 | (defn -getMinorVersion 256 | "Just delegates to the referenced/internal driver (see `-init`)." 257 | 258 | [this] 259 | (.getMinorVersion (.state this))) 260 | 261 | (defn -jdbcCompliant 262 | "Just delegates to the referenced/internal driver (see `-init`)." 263 | 264 | [this] 265 | (.jdbcCompliant (.state this))) 266 | 267 | (defn -getParentLogger 268 | "Just delegates to the referenced/internal driver (see `-init`)." 269 | 270 | [this] 271 | (.getParentLogger (.state this))) 272 | 273 | (defn eval-buttle-user-file! 274 | "If system property `buttle.user-file` is set, uses `load-file` to 275 | evaluate that file. 276 | 277 | This function is called when namespace `buttle.driver` is 278 | loaded. This happens for example when die _Buttle_ JDBC driver is 279 | loaded. 280 | 281 | Use this function to load your own code when you do not control the 282 | main program flow (like when using _Buttle_ in tools like SQuirreL 283 | or in a Java application server when you do not control/own the main 284 | application)." 285 | 286 | [] 287 | (when-let [user-file (System/getProperty "buttle.user-file")] 288 | (try 289 | (load-file user-file) 290 | (catch Throwable t 291 | (.println System/err (format "(eval-buttle-user-file! %s) failed: %s" (pr-str user-file) t)))))) 292 | 293 | ;; Note - this will execute when lein compiling, but should do no harm 294 | (eval-buttle-user-file!) 295 | -------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.proxy.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.proxy documentation

buttle.proxy

A proxy factory.

4 |

This namespace delivers all functions needed to (generically) create proxies for JDBC related interfaces and to implement the delegation logic for these proxies that is needed to route method calls through to the real JDBC driver’s instances.

5 |

In order to hook your own code into the delegation use def-handle to register your functions for certain method calls.

->invoke-event

(->invoke-event the-method target-obj the-args)

Returns an invoke-event-map.

->return-event

(->return-event invoke-evt r)

Returns a return-event-map.

->throw-event

(->throw-event invoke-evt t)

Returns a throw-event-map.

def-handle

macro

(def-handle [clss mthd] [the-method target-obj the-args] body)

Registers a handle method implementation for dispatch (as of invocation-key) value [clss mthd]. Can be undone via remove-handle. Re-registering just overwrites.

6 |

Uses fix-prefers! on the given key. So you may use keys like (a) [Object :buttle/getCatalog] and (b) [java.sql.Connection :buttle/default] to register an implementation for (a) specific method names (ignoring the defining class/interface) and (b) specific interfaces ignoring the method name (with a preference for (b) in conflicting cases).

7 |

The most specific registration would be [java.sql.Connection 8 | :buttle/getCatalog]. So this would be prefered over (a) and (b) when present.

9 |

Note: This macro (i.e. the resulting code) may not be not thread-safe because it uses fix-prefers! which may not be thread-safe. You should use def-handle only in top-level-forms for defining handle method-implemenations but not in functions you call as part of the program flow.

fix-prefers!

(fix-prefers! [clss mthd])

If (= mthd :buttle/default) makes all/any method m of class clss (via derive) a child of :buttle/default and prefers [clss :buttle/default] over [java.lang.Object m]. This lets you dispatch via invocation-key with an inheritance mechanism which uses/combines isa? on types (isa? Connection Object) and on method keys (isa? :buttle/getCatalog :buttle/default).

10 |

Now you can (a) def-handle [Object :buttle/getCatalog] and (b) def-handle [java.sql.Connection :buttle/default] with a preference for (a) when calling Connection/getCatalog.

11 |

This function is thread-safe only if derive and prefer-method are so. You will usually not use this function directly but only through def-handle.

function-default-hierarchy

Atom carrying a simple/shallow hierarchy of :buttle/<method-name> to :buttle/default entries. This hierarchy is used for handle multi-method and fix-prefers!.

handle

multimethod

A generic delegation function (arity [the-method target-obj 12 | the-args]) which delivers proxy’ed return values.

13 |

The :default implementation just delegates to handle-default.

14 |

The multi-method dispatch is done on invocation-key.

15 |

This is a multi-method so that you have a means (see def-handle) to hook into the execution/delegation/proxying logic for some/any of the proxy’ed interface types i.e. invocation-key dispatch values.

handle-default

(handle-default the-method target-obj the-args)

Calls the-method on target-obj with the-args, creates a proxy via make-proxy (which uses handle as its handler-fn) for non-nil interface-typed return values and returns the (possibly proxy’ed) result. Throws if the invoked method throws.

16 |

Sends (buttle.event/send-event) an ->invoke-event before calling the-method. Sends a ->throw-event if the-method call throws. Else sends a ->return-event before result proxy is created.

invocation-key

(invocation-key the-method & _)

Dispatch function for handle. Returns a vector with the method’s declaring class and buttle/ namespaced keyword for the method’s name.

17 |

Example:

18 |
(-> java.sql.Connection
19 |     (.getMethod "close" nil)
20 |     invocation-key)
21 | ;; --> [java.sql.Connection :buttle/close]
22 | 

invoke-fn

(invoke-fn proxy-type target-obj handler-fn the-proxy the-method the-args)

Invocation handler for make-proxy. It delegates any method invocation of proxy-type (which may be an interface or a vector of interfaces) to handler-fn. Any other method invocations (like Object.toString()) will be invoked on target-object.

23 |

Note that any java.lang.reflect.InvocationTargetException (incl. those coming from handler-fn) will be un-rolled so that the cause Exception will come out of invoke-fn and thus the proxy made by make-proxy.

24 |

This function is not meant for public usage. It functions as a hookable delegation-point for make-proxy so that you may re-bind/re-def the var when debugging and hacking.

make-proxy

(make-proxy proxy-type target-obj handler-fn)

A proxy factory.

25 |

Creates and returns a Java dynamic proxy with proxy-type (which may be an interface or a vector of interfaces).

26 |

The proxy delegates any method invocation to invoke-fn which in turn delegates to handler-fn.

27 |

The classloader for this proxy is taken from the first interface. If you want to delegate invocations on this proxy D through handler-fn and onto a Clojure proxy P you have to make sure, that D’s and P’s method/class declarations are compatible. See notes on classloading in src/buttle/connection_pool_data_source.clj for more details.

28 |

Example usage:

29 |
 (make-proxy java.sql.Driver "foo-driver"
30 |   (fn [the-method target-obj the-args]
31 |     (condp = (.getName the-method)
32 |       "acceptsURL" true
33 |       "connect" (proxy [java.sql.Connection] []
34 |                   (toString [] "foo-connection")))))
35 | 

methods-of

(methods-of clss)

Returns seq of :buttle/ namespaced method names of class clss.

remove-handle

(remove-handle [clss mthd])

Removes the handle for [clss mthd]. No-op if there is no registered handle for this key.

-------------------------------------------------------------------------------- /resources/public/generated-doc/buttle.driver.html: -------------------------------------------------------------------------------- 1 | 3 | buttle.driver documentation

buttle.driver

The Buttle java.sql.Driver.

4 |

This namespace delivers buttle.jdbc.Driver via :gen-class. This named class can be used by tools (like SQuirreL) and the SPI services/java.sql.Driver.

5 |

The -init constructor function will register a Buttle Driver proxy (see make-driver) with the java.sql.DriverManager. So whenever an instance of buttle.jdbc.Driver is created, a new proxy ( not the buttle.jdbc.Driver!) is registered.

6 |

Example JDBC-URL:

7 |
jdbc:buttle:{:user "<user>" :password "<password>" :target-url "jdbc:postgresql://127.0.0.1:6632/postgres"}
 8 | 
9 |

Example Wildfly datasource (see README for more details):

10 |
  <datasource jndi-name="java:/jdbc/buttle-ds" pool-name="buttle-ds" use-java-context="true">
11 |       <driver>buttle-driver</driver>
12 |       <connection-url>
13 |               jdbc:buttle:{
14 |                       :user "<user>"
15 |                       :password "<password>"
16 |                       :class-for-name "org.postgresql.Driver"
17 |                       :target-url "jdbc:postgresql://<host>:<port>/<db-id>"}
18 |           </connection-url>
19 |   </datasource>
20 | 
21 |

When this namespace is loaded eval-buttle-user-file! will be executed.

22 |

WARNING: this means that anyone who controls the system properties of the hosting JVM can run any Clojure code (but then again — when someone controls the system properties he/she is probably able to run any command anyway).

23 |

Functions in this namespace deliver all the functionality needed for the java.sql.Driver interface/contract. Things for connections, statements etc. are all delivered through buttle.proxy.

-acceptsURL

(-acceptsURL this url)

Implements java.sql.Driver.acceptsURL(String). Just delegates to the referenced/internal driver (see -init).

-connect

(-connect this url info)

Implements java.sql.Driver.connect(String, Properties). Just delegates to the referenced/internal driver (see -init) which calls connect-fn.

-getMajorVersion

(-getMajorVersion this)

Just delegates to the referenced/internal driver (see -init).

-getMinorVersion

(-getMinorVersion this)

Just delegates to the referenced/internal driver (see -init).

-getParentLogger

(-getParentLogger this)

Just delegates to the referenced/internal driver (see -init).

-getPropertyInfo

(-getPropertyInfo this url info)

Just delegates to the referenced/internal driver (see -init).

-init

Constructor function of buttle.jdbc.Driver.

24 |

On first invokation creates a Buttle Driver (see make-driver), keeps a reference to it and registers it with the java.sql.DriverManager.

25 |

This Buttle driver becomes the internal state of the buttle.jdbc.Driver. All Driver method implemenations of this class delegate to this internal driver.

-jdbcCompliant

(-jdbcCompliant this)

Just delegates to the referenced/internal driver (see -init).

accepts-url-fn

(accepts-url-fn url)

Parses url via parse-jdbc-url and retrieves the keys :target-url, :user, :password, :class-for-name and :datasource-spec from the returned value (assuming that it is a map). Returns a map with those keys/values.

connect-fn

(connect-fn url {info-user "user", info-password "password", :as info})

Returns a JDBC connection for given url.

26 |

Returns nil if url is not a Buttle URL (as of accepts-url-fn). Else opens a JDBC Connection to :target-url via buttle.driver-manager/get-connection. If that throws then this function throws. Otherwise the connection is returned. Note that in this case Buttle does not call/use the proxied JDBC driver directly (but relies on the DriverManager).

27 |

If :datasource-spec is given in the Buttle URL a datasource ds (as of buttle-ds/retrieve-data-soure) will be used instead of the buttle.driver-manager/get-connection.

28 |

Authentication credentials (user and password) are taken from info (keys "user" and "password") if given. Else they are taken from Buttle URL (keys :user and :password). If the connection is opened via DriverManager then info will be passed onto DriverManager/getConnection so that any properties beyond "user" and "password" (which are set to the authentication credentials) contained in info are preserved.

29 |

If :class-for-name is set in the Buttle url then calls (Class/forName class-for-name) before opening the connection. This takes care of cases when the proxied driver is not loaded auto-magically by the DriverManager. This may happen when the DriverManager is initialized and the classloader does not see the proxied driver classes at that point (e.g. in JEE application servers when loading things via isolated classloaders).

eval-buttle-user-file!

(eval-buttle-user-file!)

If system property buttle.user-file is set, uses load-file to evaluate that file.

30 |

This function is called when namespace buttle.driver is loaded. This happens for example when die Buttle JDBC driver is loaded.

31 |

Use this function to load your own code when you do not control the main program flow (like when using Buttle in tools like SQuirreL or in a Java application server when you do not control/own the main application).

make-driver

(make-driver)

Creates and returns a Buttle java.sql.Driver.

32 |

Note that the underlying driver (which delegates to the real driver) is a Clojure proxy (not an instance of buttle.jdbc.Driver) which is wrapped by a buttle.proxy/make-proxy. So calls to retured driver can be intercepted by buttle.proxy/def-handle.

33 |

This driver can be registered with the java.sql.DriverManager. There are two important methods that this driver (proxy) implements: connect and acceptsURL. These are needed for interaction with the DriverManager so that the Buttle driver can be picked up for Buttle URLs.

34 |

Note: the Buttle proxy will set the current thread’s context classloader (TCCL) to Buttle’s classloader when delegating to buttle.proxy/handle. This is needed for cases when Buttle is used as a data-source and deployed as a module in Wildfly/JBoss application server. In this case clojure.lang.RT tries to load clojure/core.clj which it can’t because it uses the current thread’s context classloader and for Wildfly that will not be the Buttle module’s classloader. So we explicitly set the TCCL and things work. In non-application-server cases this usually does not hurt. At the moment you cannot configure this feature.

parse-jdbc-url

(parse-jdbc-url url)

Parses a Buttle JDBC URL.

35 |

A Buttle JDBC URL has the format #"jdbc:buttle:(.+)". Any text after jdbc:buttle: will be read-string-eval’ed and should yield a map with keys :target-url, :user and :password. The eval’ed value is returned (even if not a map). If url does not match the pattern nil is returned. If read-string-eval throws an Exception then an Exception will be thrown.

--------------------------------------------------------------------------------