JVM-specific utilities and helpers. You don't want to import this namespace directly - these functions are re-exported by [[metabase.util]]. | (ns metabase.util.jvm (:require [clojure.java.classpath :as classpath] [clojure.string :as str] [clojure.tools.namespace.find :as ns.find] [metabase.util.format :as u.format] [metabase.util.log :as log]) (:import (java.net InetAddress InetSocketAddress Socket) (java.util Base64 Base64$Decoder Base64$Encoder Locale PriorityQueue) (java.util.concurrent TimeoutException))) |
(set! *warn-on-reflection* true) | |
Make a properly-tagged Java interop varargs argument. This is basically the same as (u/varargs String) (u/varargs String ["A" "B"]) | (defmacro varargs {:style/indent [:defn], :arglists '([klass] [klass xs])} [klass & [objects]] (vary-meta `(into-array ~klass ~objects) assoc :tag (format "[L%s;" (.getTypeName ^Class (ns-resolve *ns* klass))))) |
Timeout (in ms) for checking if a host is available with | (def ^:private ^:const host-up-timeout 5000) |
Returns true if the port is active on a given host, false otherwise | (defn host-port-up? [^String hostname, ^Integer port] (try (let [sock-addr (InetSocketAddress. hostname port)] (with-open [sock (Socket.)] (.connect sock sock-addr host-up-timeout) true)) (catch Throwable _ false))) |
Returns true if the host given by hostname is reachable, false otherwise | (defn host-up? [^String hostname] (try (let [host-addr (InetAddress/getByName hostname)] (.isReachable host-addr host-up-timeout)) (catch Throwable _ false))) |
(defprotocol ^:private IFilteredStacktrace (filtered-stacktrace [this] "Get the stack trace associated with E and return it as a vector with non-metabase frames after the last Metabase frame filtered out.")) | |
(extend-protocol IFilteredStacktrace nil (filtered-stacktrace [_] nil) Throwable (filtered-stacktrace [^Throwable this] (filtered-stacktrace (.getStackTrace this))) Thread (filtered-stacktrace [^Thread this] (filtered-stacktrace (.getStackTrace this)))) | |
(extend (Class/forName "[Ljava.lang.StackTraceElement;") IFilteredStacktrace {:filtered-stacktrace (fn [this] ;; keep all the frames before the last Metabase frame, but then filter out any other non-Metabase frames after ;; that (let [[frames-after-last-mb other-frames] (split-with #(not (str/includes? % "metabase")) (seq this)) [last-mb-frame & frames-before-last-mb] (for [frame other-frames :when (str/includes? frame "metabase")] (str/replace frame #"^metabase\." ""))] (vec (concat (map str frames-after-last-mb) ;; add a little arrow to the frame so it stands out more (cons (some->> last-mb-frame (str "--> ")) frames-before-last-mb)))))}) | |
Whether string | (defn ip-address? [s] (and (string? s) (.isValid (org.apache.commons.validator.routines.InetAddressValidator/getInstance) ^String s))) |
A reducing function that maintains a queue of the largest items as determined by In general, (= (take-last 2 (sort-by identity kompare coll)) (transduce (map identity) (u/sorted-take 2 kompare) coll)) But the entire collection is not in memory, just at most | (defn sorted-take [size kompare] (fn bounded-heap-acc ([] (PriorityQueue. size kompare)) ([^PriorityQueue q] (loop [acc []] (if-let [x (.poll q)] (recur (conj acc x)) acc))) ([^PriorityQueue q item] (if (>= (.size q) size) (let [smallest (.peek q)] (if (pos? (kompare item smallest)) (doto q (.poll) (.offer item)) q)) (doto q (.offer item)))))) |
Gather the full exception chain into a sequence. | (defn full-exception-chain [e] (when (instance? Throwable e) (take-while some? (iterate ex-cause e)))) |
Like (def e (ex-info "A" {:a true, :both "a"} (ex-info "B" {:b true, :both "A"}))) (ex-data e) ;; -> {:a true, :both "a"} (u.jvm/all-ex-data e) ;; -> {:a true, :b true, :both "a"} | (defn all-ex-data [e] (reduce (fn [data e] (merge (ex-data e) data)) nil (full-exception-chain e))) |
Execute Consider using the For implementing more fine grained retry policies like exponential backoff,
consider using the | (defn do-with-auto-retries [num-retries f] (if (<= num-retries 0) (f) (try (f) (catch Throwable e (when (::no-auto-retry? (all-ex-data e)) (throw e)) (log/warn (u.format/format-color 'red "auto-retry %s: %s" f (.getMessage e))) (do-with-auto-retries (dec num-retries) f))))) |
Execute You can disable auto-retries for a specific ExceptionInfo by including For implementing more fine grained retry policies like exponential backoff,
consider using the | (defmacro auto-retry {:style/indent 1} [num-retries & body] `(do-with-auto-retries ~num-retries (fn [] ~@body))) |
A shared Base64 decoder instance. | (def ^:private ^Base64$Decoder base64-decoder (Base64/getDecoder)) |
Decodes a Base64 string into bytes. | (defn decode-base64-to-bytes ^bytes [^String string] (.decode base64-decoder string)) |
Decodes the Base64 string TODO -- this is only used [[metabase.analytics.snowplow-test]] these days | (defn decode-base64 [input] (new java.lang.String (decode-base64-to-bytes input) "UTF-8")) |
A shared Base64 encoder instance. | (def ^:private ^Base64$Encoder base64-encoder (Base64/getEncoder)) |
Encodes the UTF-8 encoding of the string | (defn encode-base64 ^String [^String input] (.encodeToString base64-encoder (.getBytes input "UTF-8"))) |
(def ^:private do-with-us-locale-lock (Object.)) | |
Implementation for | (defn do-with-us-locale [f] ;; Since I'm 99% sure default Locale isn't thread-local we better put a lock in place here so we don't end up with ;; the following race condition: ;; ;; Thread 1 ....*.............................*........................*...........* ;; ^getDefault() -> Turkish ^setDefault(US) ^(f) ^setDefault(Turkish) ;; Thread 2 ....................................*....................*................*......* ;; ^getDefault() -> US ^setDefault(US) ^(f) ^setDefault(US) (locking do-with-us-locale-lock (let [original-locale (Locale/getDefault)] (try (Locale/setDefault Locale/US) (f) (finally (Locale/setDefault original-locale)))))) |
Execute ;; Locale is Turkish / -Duser.language=tr (.toUpperCase "filename") ;; -> "FÄ°LENAME" Rather than submit PRs to every library in the world to use Note that because DO NOT use this macro in API endpoints or other places that are multithreaded or performance will be negatively
impacted. (You shouldn't have a good reason for using this there anyway. Rewrite your code to pass | (defmacro with-us-locale {:style/indent 0} [& body] `(do-with-us-locale (fn [] ~@body))) |
Vector of symbols of all Metabase namespaces, excluding test namespaces. This is intended for use by various routines that load related namespaces, such as task and events initialization. This is made | (defonce ^:const metabase-namespace-symbols (vec (sort (for [ns-symb (ns.find/find-namespaces (classpath/system-classpath)) :when (and (str/starts-with? ns-symb "metabase") (not (str/includes? ns-symb "test")))] ns-symb)))) |
Call | (defn deref-with-timeout [reff timeout-ms] (let [result (deref reff timeout-ms ::timeout)] (when (= result ::timeout) (when (future? reff) (future-cancel reff)) (throw (TimeoutException. (format "Timed out after %s" (u.format/format-milliseconds timeout-ms))))) result)) |
Impl for | (defn do-with-timeout [timeout-ms f] (try (deref-with-timeout (future-call f) timeout-ms) (catch java.util.concurrent.ExecutionException e (throw (.getCause e))))) |
Run | (defmacro with-timeout [timeout-ms & body] `(do-with-timeout ~timeout-ms (fn [] ~@body))) |
Returns (u/poll {:thunk (fn [] (upload!)) :done (fn [response] (get-in response [:status :done])) :timeout-ms 1000 :interval-ms 100}) | (defn poll [{:keys [thunk done? timeout-ms interval-ms] :or {timeout-ms 1000 interval-ms 100}}] (let [start-time (System/currentTimeMillis)] (loop [] (let [response (thunk)] (if (done? response) response (let [current-time (System/currentTimeMillis) elapsed-time (- current-time start-time)] (if (>= elapsed-time timeout-ms) nil ; timeout reached (do (Thread/sleep (long interval-ms)) (recur))))))))) |
Parse a currency String to a BigDecimal. Handles a variety of different formats, such as: $1,000.00 -£127.54 -127,54 € kr-127,54 € 127,54- ¥200 Following function is not compatible with Safari 16.3 and older because it uses lookbehind regex. | (defn parse-currency ^java.math.BigDecimal [^String s] (when-not (str/blank? s) (bigdec (reduce (partial apply str/replace) s [;; strip out any current symbols [#"[^\d,.-]+" ""] ;; now strip out any thousands separators [#"(?<=\d)[,.](\d{3})" "$1"] ;; now replace a comma decimal seperator with a period [#"," "."] ;; move minus sign at end to front [#"(^[^-]+)-$" "-$1"]])))) |