Last active
May 11, 2024 21:40
-
-
Save henrik42/3583db3a49d29253d004bae1b5cfc0c5 to your computer and use it in GitHub Desktop.
Mutate Java Environment Variables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; There are ways to change the environent variables of a running process | |
;; (https://unix.stackexchange.com/questions/38205/change-environment-of-a-running-process). | |
;; This is also true for a JVM process. | |
;; | |
;; Java has an API to the environment (java.lang.ProcessEnvironment). This class | |
;; reads the JVM process environment once and stores the key/values in a few | |
;; maps. From this point on, these maps are Java's view on its environment | |
;; variables. All access through this API (which your code may use) will read | |
;; values from these maps. | |
;; | |
;; The API gives you only read-access to these maps and will not change them | |
;; once they are created. So they are effectivly immutable and "fixed". Using | |
;; reflective access to the static fields holding references to these mutable | |
;; maps we can access and mutate the maps. | |
;; | |
;; In this case we are NOT changing the JVM process environment. We are only | |
;; changing the view of the API any Java class will be using. | |
;; | |
;; This code was inspired by | |
;; https://blog.sebastian-daschner.com/entries/changing_env_java and | |
;; https://stackoverflow.com/questions/318239/how-do-i-set-environment-variables-from-java. | |
;; | |
;; Notes: | |
;; * put! and remove! are not thread-safe -- i.e. there is a race between the | |
;; put/remove and the putAll where users of the API may see inconsistent | |
;; values. | |
;; * put! and remove! do not flush to main memory so users from other threads | |
;; may see inconsistent values. | |
;; * put! and remove! mutate the maps in-place to that iterators on those maps | |
;; may brake and throw ConcurrentModificationException. | |
(defn static-field-value [clazz field-name] | |
(-> clazz | |
(.getDeclaredField (name field-name)) | |
(doto (.setAccessible true)) | |
(.get nil))) | |
(def the-environment | |
(static-field-value java.lang.ProcessEnvironment :theEnvironment)) | |
(def the-case-insensitive-environment | |
(static-field-value java.lang.ProcessEnvironment :theCaseInsensitiveEnvironment)) | |
(defn put! [k v] | |
(.put ^java.util.HashMap the-environment k v) | |
(.clear the-case-insensitive-environment) | |
(.putAll the-case-insensitive-environment the-environment)) | |
(defn remove! [k] | |
(.remove ^java.util.HashMap the-environment k) | |
(.clear the-case-insensitive-environment) | |
(.putAll the-case-insensitive-environment the-environment)) | |
(comment | |
(def the-unmodifiable-environment (static-field-value java.lang.ProcessEnvironment :theUnmodifiableEnvironment)) | |
(defn assert-all-equal [& optional] | |
(assert (apply = | |
the-unmodifiable-environment | |
the-environment | |
the-case-insensitive-environment | |
(System/getenv) | |
(.clone the-environment) | |
(.clone the-case-insensitive-environment) | |
optional))) | |
(put! "FOO" "BAR") | |
(assert-all-equal) | |
(assert (= "BAR" (System/getenv "FOO"))) | |
(put! "FOO" "BAZ") | |
(assert-all-equal) | |
(assert (= "BAZ" (System/getenv "FOO"))) | |
(put! "FOOBAR" "QUOX") | |
(assert-all-equal) | |
(assert (= "QUOX" (System/getenv "FOOBAR"))) | |
(remove! "foobar") | |
(assert-all-equal) | |
(remove! "FOOBAR") | |
(assert-all-equal) | |
(assert (nil? (System/getenv "FOOBAR"))) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment