├── .gitignore ├── src └── clojure │ └── data │ └── alpha │ └── replicant │ └── server │ ├── impl │ ├── protocols.clj │ └── cache.clj │ ├── reader.clj │ ├── prepl.clj │ └── spi.clj ├── deps.edn ├── test └── data │ └── replicant │ └── server │ └── test_spi.clj ├── API.md ├── readme.md └── epl-v10.html /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .idea/ 3 | .nrepl* 4 | *.iml 5 | .clj-kondo/ 6 | .calva/ 7 | .lsp/ 8 | .vscode/ 9 | -------------------------------------------------------------------------------- /src/clojure/data/alpha/replicant/server/impl/protocols.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.data.alpha.replicant.server.impl.protocols) 10 | 11 | (defprotocol HasRemote 12 | (-has-remotes? [x] "Returns true if the -remotify of x will contain any remote refs.")) 13 | 14 | (defprotocol Remotify 15 | (-remotify [_ server] "If the object is remotable, returns its ref, else self.")) 16 | 17 | (defprotocol Cache 18 | (-put [cache key object] "Given a cache instant, key, and object; puts object at key.") 19 | (-get [cache key] "Given key, return mapped value or nil if key not found.")) 20 | 21 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps 3 | {org.clojure/clojure {:mvn/version "1.11.1"} 4 | com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.1"}} 5 | 6 | :aliases 7 | {:test {:extra-paths ["test"] 8 | :extra-deps {io.github.cognitect-labs/test-runner 9 | {:git/url "https://github.com/cognitect-labs/test-runner.git" 10 | :sha "9e35c979860c75555adaff7600070c60004a0f44"}} 11 | :main-opts ["-m" "cognitect.test-runner"] 12 | :exec-fn cognitect.test-runner.api/test} 13 | :quickdoc 14 | {:deps {org.babashka/cli {:mvn/version "0.4.36"} 15 | io.github.borkdude/quickdoc 16 | {:deps/root "jvm" 17 | :git/sha "c5320cbe311b651a60b47f4d00d7e8ab63291b6e"}} 18 | :main-opts ["-m" "babashka.cli.exec" "quickdoc.api" "quickdoc"] 19 | :exec-args {:github/repo "https://github.com/clojure/data.alpha.replicant-server" 20 | :git/branch "main" 21 | :source-paths ["src/clojure/data/alpha/replicant/server/prepl.clj"]}} 22 | :server {:ns-default clojure.data.alpha.replicant.server.prepl 23 | :exec-fn start}}} 24 | -------------------------------------------------------------------------------- /src/clojure/data/alpha/replicant/server/reader.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.data.alpha.replicant.server.reader 10 | (:require 11 | [clojure.data.alpha.replicant.server.spi :as spi])) 12 | 13 | (set! *warn-on-reflection* true) 14 | 15 | (defn lid-reader 16 | "Read '#l/id id' and return the cached object" 17 | [rid] 18 | `(let [val# (spi/rid->object spi/*rds-cache* ~rid) 19 | mval# (if (not (nil? val#)) 20 | (if (instance? clojure.lang.IObj val#) 21 | (with-meta val# {:r/id ~rid}) 22 | val#) 23 | (throw (ex-info (str "Remote data structure not found in cache for rid " ~rid) 24 | {:id ~rid})))] 25 | mval#)) 26 | -------------------------------------------------------------------------------- /test/data/replicant/server/test_spi.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns data.replicant.server.test-spi 10 | (:require 11 | [clojure.test :refer :all] 12 | [data.replicant.server.impl.cache :as server.cache] 13 | [data.replicant.server.spi :as server.spi])) 14 | 15 | (deftest remote-and-print 16 | (let [cache-builder (doto (com.github.benmanes.caffeine.cache.Caffeine/newBuilder) (.softValues)) 17 | cache (server.cache/create-remote-cache cache-builder)] 18 | (binding [server.spi/*rds-cache* (constantly cache) 19 | *print-meta* true] 20 | (are [expected-str val] 21 | (= expected-str (-> val (server.spi/remotify cache) pr-str)) 22 | "[1 2]" [1 2] 23 | "" (vec (range 500)) 24 | )))) 25 | 26 | (comment 27 | (remote-and-print) 28 | 29 | ) 30 | -------------------------------------------------------------------------------- /src/clojure/data/alpha/replicant/server/impl/cache.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.data.alpha.replicant.server.impl.cache 10 | (:require 11 | [clojure.data.alpha.replicant.server.impl.protocols :as p]) 12 | (:import 13 | [com.github.benmanes.caffeine.cache Caffeine Cache RemovalListener] 14 | [java.util UUID] 15 | [java.util.function Function] 16 | [java.util.concurrent ConcurrentMap ConcurrentHashMap])) 17 | 18 | (set! *warn-on-reflection* true) 19 | 20 | (defn ju-function 21 | "Wrap Clojure function as j.u.Function." 22 | ^Function [f] 23 | (reify Function 24 | (apply [_ x] (f x)))) 25 | 26 | (defn- gc-removed-value-listener 27 | "Return a cache removal listener that calls f on the removed value." 28 | ^RemovalListener [f] 29 | (reify RemovalListener 30 | (onRemoval [_this _k v _cause] (f v)))) 31 | 32 | (def ^ConcurrentMap identity->rid (ConcurrentHashMap.)) 33 | 34 | ;; create with create-remote-cache 35 | (deftype RemoteCache 36 | [^Cache cache] 37 | p/Cache 38 | (-put 39 | [_ k obj] 40 | (.put cache k obj)) 41 | (-get 42 | [_ k] 43 | (.getIfPresent cache k))) 44 | 45 | (defn create-remote-cache 46 | "Given a caffeine cache builder, return a p/Cache that uses uuids for remote ids." 47 | [^Caffeine builder] 48 | (let [rid->obj (.. builder 49 | (removalListener 50 | (-> (fn [obj] 51 | (.remove identity->rid (System/identityHashCode obj))) 52 | gc-removed-value-listener)) 53 | build)] 54 | (RemoteCache. rid->obj))) 55 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # clojure.data.alpha.replicant.server.prepl 2 | 3 | 4 | 5 | 6 | 7 | ## `datafy` 8 | ``` clojure 9 | 10 | (datafy v) 11 | ``` 12 | 13 | 14 | Remote API: Called by a replicant client to retrieve a datafy representation for an object v. 15 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L147-L150) 16 | ## `entry` 17 | ``` clojure 18 | 19 | (entry m k) 20 | (entry 21 | m 22 | k 23 | {:keys [rds/lengths rds/level], 24 | :as depth-opts, 25 | :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}) 26 | ``` 27 | 28 | 29 | Remote API: Called by a replicant client to retrieve a value mapped at key k for a collection m. Takes an 30 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 31 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value. 32 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L119-L138) 33 | ## `fetch` 34 | ``` clojure 35 | 36 | (fetch v) 37 | (fetch 38 | v 39 | {:keys [rds/lengths rds/level], 40 | :as depth-opts, 41 | :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}) 42 | ``` 43 | 44 | 45 | Remote API: Called by a replicant client to retrieve an object from the cache. Takes an 46 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 47 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value. 48 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L88-L103) 49 | ## `rds-prepl` 50 | ``` clojure 51 | 52 | (rds-prepl rds-cache) 53 | ``` 54 | 55 | 56 | Uses *in* and *out* streams to serve RDS data given an RDS cache. 57 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L53-L58) 58 | ## `remotify-proc` 59 | ``` clojure 60 | 61 | (remotify-proc val) 62 | ``` 63 | 64 | [source](src/clojure/data/alpha/replicant/server/prepl.clj#L31-L34) 65 | ## `seq` 66 | ``` clojure 67 | 68 | (seq v) 69 | (seq 70 | v 71 | {:keys [rds/lengths rds/level], 72 | :as depth-opts, 73 | :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}) 74 | ``` 75 | 76 | 77 | Remote API: Called by a replicant client to retrieve a seq for a collection. Takes an 78 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 79 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value. 80 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L105-L117) 81 | ## `start` 82 | ``` clojure 83 | 84 | (start) 85 | (start & {:keys [port cache server-name], :or {port 5555, server-name "rds"}}) 86 | ``` 87 | 88 | 89 | Local API: Starts a named Replicant server in the current process on a connected socket client thread. 90 | By default this function starts a server named "rds" listening on localhost:5555 and initializes a 91 | default Replicant cache. Callers may pass an options map with keys :server-name, :port, and :cache to 92 | override those defaults. 93 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L60-L75) 94 | ## `stop` 95 | ``` clojure 96 | 97 | (stop server-name) 98 | ``` 99 | 100 | 101 | Local API: Stops a named Replicant server in the current process. Stopping an active Replicant server 102 | closes all clients connected to it and clears its remote data cache. 103 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L77-L81) 104 | ## `string` 105 | ``` clojure 106 | 107 | (string v) 108 | ``` 109 | 110 | 111 | Remote API: Called by a replicant client to retrieve a string representation for an object v. 112 |
[source](src/clojure/data/alpha/replicant/server/prepl.clj#L142-L145) 113 | 114 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # replicant-server 2 | 3 | A Clojure library providing remote implementations of the Clojure data structures and a remote REPL server hosted over prepl. 4 | 5 | This software is considered an alpha release and subject to change. 6 | 7 | ## Rationale 8 | 9 | While the ability to connect to Clojure REPLs over a wire is a powerful lever for programmers, the transport of data over an active connection is problemmatic in numerous ways: 10 | 11 | - Transport of large or deep collection is slow 12 | - Transport of lazy sequences forces realization 13 | - Not all objects are able to transport, some examples being: 14 | - Objects with types unavailble to both sides of the wire 15 | - Objects holding local resources (e.g. files, sockets, etc.) 16 | - Functions 17 | - Not all objects are printable, and therefore unable to transport 18 | 19 | Replicant works to alleviate the issues outlined above by providing the following capabilities: 20 | 21 | - Replicant transports large collections by passing partial data over the wire. On the other side, Replicant constructs Remote Data Structures that act as Clojure data for read-only operations and request more data as needed 22 | - Replicant transports only the head of lazy sequences and on the other side can fetch more data as needed 23 | - Replicant handles objects that cannot transport by constructing remote references that may later serve as handles for evaluation when requested 24 | - Replicant transports function references that allow remote invocation 25 | - Replicant uses a Datafy context to allow extensibility in the way that objects print on the wire 26 | 27 | To this end Replicant provides two libraries: [Replicant Server](https://github.com/clojure/data.alpha.replicant-server) (this library) and [Replicant Client](https://github.com/clojure/data.alpha.replicant-client). 28 | 29 | ## Docs 30 | 31 | * [API.md](API.md) 32 | 33 | # Release Information 34 | 35 | Latest release: 36 | 37 | [deps.edn](https://clojure.org/reference/deps_and_cli) dependency information: 38 | 39 | The replicant-server library is intended for use as a git dep: 40 | 41 | ```clojure 42 | io.github.clojure/data.alpha.replicant-server {:git/tag "v2023.05.02.01" :git/sha "947d8a8"} 43 | ``` 44 | 45 | ## Usage 46 | 47 | replicant-server is meant to run in-process. Once added as a dependency, the following will launch an embedded remote PREPL awaiting a [replicant-client](https://github.com/clojure/data.alpha.replicant-client) or socket connection. 48 | 49 | ```clojure 50 | (require '[clojure.data.alpha.replicant.server.prepl :as replicant]) 51 | (replicant/start :host "hostname") 52 | ``` 53 | 54 | The function `start` takes a map of options allowing customized values for `:port` and `:name` parameters. By default the function runs as if the following was passed: 55 | 56 | ```clojure 57 | (replicant/start {:host "hostname", :name "rds", :port 5555}) 58 | ``` 59 | 60 | You can stop a named Replicant server using the `stop-replicant` function, passing the name given it at start time: 61 | 62 | ```clojure 63 | (replicant/stop "rds") 64 | ``` 65 | 66 | Stopping an active Replicant server will close all clients connected to it and clear its remote data cache. 67 | 68 | # Developer Information 69 | 70 | * [GitHub project](https://github.com/clojure/data.alpha.replicant-server) 71 | * [How to contribute](https://clojure.org/community/contributing) 72 | * [Bug Tracker](https://clojure.atlassian.net/browse/DRDS) 73 | 74 | # Copyright and License 75 | 76 | Copyright © 2023 Rich Hickey and contributors 77 | 78 | All rights reserved. The use and 79 | distribution terms for this software are covered by the 80 | [Eclipse Public License 1.0] which can be found in the file 81 | epl-v10.html at the root of this distribution. By using this software 82 | in any fashion, you are agreeing to be bound by the terms of this 83 | license. You must not remove this notice, or any other, from this 84 | software. 85 | 86 | [Eclipse Public License 1.0]: http://opensource.org/licenses/eclipse-1.0.php 87 | -------------------------------------------------------------------------------- /src/clojure/data/alpha/replicant/server/prepl.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.data.alpha.replicant.server.prepl 10 | (:require 11 | [clojure.data.alpha.replicant.server.spi :as server.spi] 12 | [clojure.data.alpha.replicant.server.reader :as server.reader] 13 | [clojure.data.alpha.replicant.server.impl.cache :as server.cache] 14 | [clojure.core.server :as server] 15 | [clojure.datafy :as d]) 16 | (:import 17 | [clojure.lang MapEntry]) 18 | (:refer-clojure :exclude [seq])) 19 | 20 | (set! *warn-on-reflection* true) 21 | 22 | (defn- create-default-cache 23 | "Establishes an RDS environment by building an RDS cache that LRU evicts values based on 24 | memory demands and installs the server-side readers. Returns the cache." 25 | [] 26 | (let [cache-builder (doto (com.github.benmanes.caffeine.cache.Caffeine/newBuilder) 27 | (.softValues))] 28 | (server.cache/create-remote-cache cache-builder))) 29 | 30 | ;; expects bound: server.spi/*rds-cache* 31 | (defn remotify-proc [val] 32 | (let [obj (server.spi/remotify val server.spi/*rds-cache*)] 33 | (binding [*print-meta* true] 34 | (pr-str obj)))) 35 | 36 | (defn- outfn-proc [out rds-cache] 37 | (let [lock (Object.)] 38 | (fn [m] 39 | (binding [*out* out, *flush-on-newline* true, *print-readably* true 40 | server.spi/*rds-cache* rds-cache] 41 | (locking lock 42 | (prn (if (#{:ret :tap} (:tag m)) 43 | (let [{:keys [rds/lengths rds/level] :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}} (:depth-opts (meta (:val m)))] 44 | (binding [server.spi/*remote-lengths* lengths 45 | server.spi/*remote-depth* level] 46 | (try 47 | (assoc m :val (remotify-proc (:val m))) 48 | (catch Throwable ex 49 | (assoc m :val (remotify-proc (Throwable->map ex)) 50 | :exception true))))) 51 | m))))))) 52 | 53 | (defn rds-prepl 54 | "Uses *in* and *out* streams to serve RDS data given an RDS cache." 55 | [rds-cache] 56 | (binding [server.spi/*rds-cache* rds-cache 57 | *data-readers* (assoc *data-readers* 'l/id server.reader/lid-reader)] 58 | (server/prepl *in* (outfn-proc *out* rds-cache)))) 59 | 60 | (defn start 61 | "Local API: Starts a named Replicant server in the current process on a connected socket client thread. 62 | By default this function starts a server named \"rds\" listening on localhost:5555 and initializes a 63 | default Replicant cache. Callers may pass an options map with keys :server-name, :port, and :cache to 64 | override those defaults." 65 | ([] 66 | (start nil)) 67 | ([& {:keys [host port cache server-name] :or {port 5555, server-name "rds"}}] 68 | (assert host ":host argument required and expects a string") 69 | (println "Replicant server listening on" port "...") 70 | (let [server-socket (server/start-server 71 | {:port port, 72 | :name server-name, 73 | :accept 'clojure.data.alpha.replicant.server.prepl/rds-prepl 74 | :args [(or cache (create-default-cache))] 75 | :server-daemon false})] 76 | server-socket))) 77 | 78 | (defn stop 79 | "Local API: Stops a named Replicant server in the current process. Stopping an active Replicant server 80 | closes all clients connected to it and clears its remote data cache." 81 | [server-name] 82 | (clojure.core.server/stop-server server-name)) 83 | 84 | (defn- annotate [val & {:as opts}] 85 | (if (instance? clojure.lang.IObj val) 86 | (with-meta val {:depth-opts opts}) 87 | val)) 88 | 89 | (defn fetch 90 | "Remote API: Called by a replicant client to retrieve an object from the cache. Takes an 91 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 92 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value." 93 | ([v] v) 94 | ([v {:keys [rds/lengths rds/level] :as depth-opts :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}] 95 | (if (counted? v) 96 | (if (and lengths (> (count v) (first lengths))) ;; level needed in spi 97 | (binding [server.spi/*remote-lengths* lengths 98 | server.spi/*remote-depth* level] 99 | (let [rds (server.spi/remotify v server.spi/*rds-cache*)] 100 | (if (contains? rds :id) 101 | (assoc rds :id (-> rds meta :r/id)) 102 | (annotate rds depth-opts)))) 103 | (annotate v depth-opts)) 104 | (annotate v depth-opts)))) 105 | 106 | (defn seq 107 | "Remote API: Called by a replicant client to retrieve a seq for a collection. Takes an 108 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 109 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value." 110 | ([v] (clojure.core/seq v)) 111 | ([v {:keys [rds/lengths rds/level] :as depth-opts :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}] 112 | (if (counted? v) 113 | (if (and lengths (> (count v) (first lengths))) ;; level needed in spi 114 | (binding [server.spi/*remote-lengths* lengths 115 | server.spi/*remote-depth* level] 116 | (annotate (server.spi/remotify (clojure.core/seq v) server.spi/*rds-cache*) depth-opts)) 117 | (annotate (clojure.core/seq v) depth-opts)) 118 | (annotate (clojure.core/seq v) depth-opts)))) 119 | 120 | (defn entry 121 | "Remote API: Called by a replicant client to retrieve a value mapped at key k for a collection m. Takes an 122 | object v and remotifies it if the :rds/lengths and :rds/level values cause a 123 | depth options threshold crossings. Expects a bound server.spi/*rds-cache* value." 124 | ([m k] 125 | (when (contains? m k) (MapEntry/create k (get m k)))) 126 | ([m k {:keys [rds/lengths rds/level] :as depth-opts :or {lengths server.spi/*remote-lengths*, level server.spi/*remote-depth*}}] 127 | (when (contains? m k) 128 | (let [v (get m k) 129 | retv (if (counted? v) 130 | (if (and lengths (> (count v) (first lengths))) 131 | (binding [server.spi/*remote-lengths* lengths 132 | server.spi/*remote-depth* level] 133 | (let [rds (server.spi/remotify (clojure.core/seq v) server.spi/*rds-cache*)] 134 | (if (contains? rds :id) 135 | (assoc rds :id (-> rds meta :r/id)) 136 | (annotate rds depth-opts)))) 137 | (annotate v depth-opts)) 138 | (annotate v depth-opts))] 139 | (annotate (MapEntry/create k retv) depth-opts))))) 140 | 141 | ;; remote api 142 | ;; expects bound: server.spi/*rds-cache* 143 | (defn string 144 | "Remote API: Called by a replicant client to retrieve a string representation for an object v." 145 | [v] 146 | (str v)) 147 | 148 | (defn datafy 149 | "Remote API: Called by a replicant client to retrieve a datafy representation for an object v." 150 | [v] 151 | (d/datafy v)) 152 | 153 | (comment 154 | (def svr (start :host "localhost")) 155 | (def svr (start {:port 5556})) 156 | (stop "rds") 157 | ) 158 | -------------------------------------------------------------------------------- /src/clojure/data/alpha/replicant/server/spi.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.data.alpha.replicant.server.spi 10 | (:require 11 | [clojure.data.alpha.replicant.server.impl.protocols :as proto] 12 | [clojure.data.alpha.replicant.server.impl.cache :as cache] 13 | [clojure.datafy :as d]) 14 | (:import 15 | [java.io Writer] 16 | [clojure.lang Keyword Symbol ISeq Associative IPersistentCollection MapEntry 17 | PersistentHashSet PersistentTreeSet PersistentVector IFn 18 | PersistentArrayMap PersistentHashMap PersistentStructMap PersistentTreeMap])) 19 | 20 | (set! *warn-on-reflection* true) 21 | 22 | (def ^:dynamic *rds-cache*) 23 | (def ^:dynamic *remote-lengths* [250]) 24 | (def ^:private ^:dynamic *depth-length* 250) 25 | (def ^:dynamic *remote-depth* 5) 26 | 27 | (defn object->rid 28 | [cache obj] 29 | (.computeIfAbsent 30 | cache/identity->rid 31 | (System/identityHashCode obj) 32 | (-> (fn [_] (let [rid (java.util.UUID/randomUUID)] 33 | (proto/-put cache rid obj) 34 | rid)) 35 | cache/ju-function))) 36 | 37 | (defn rid->object 38 | [cache rid] 39 | (proto/-get cache rid)) 40 | 41 | (defn has-remotes? 42 | "Returns true if remotify of obj would include remote object references." 43 | [obj] 44 | (binding [*remote-depth* (and *remote-depth* (dec *remote-depth*)) 45 | *depth-length* (or (first *remote-lengths*) *depth-length*) 46 | *remote-lengths* (next *remote-lengths*)] 47 | (or 48 | (proto/-has-remotes? obj) 49 | (proto/-has-remotes? (meta obj))))) 50 | 51 | (defn remotify 52 | "Cache obj as a remote on server. Returns uuid for the obj." 53 | [obj server] 54 | (binding [*depth-length* (or (first *remote-lengths*) *depth-length*) 55 | *remote-lengths* (next *remote-lengths*)] 56 | (let [robj (proto/-remotify obj server)] 57 | (if-let [m (meta robj)] 58 | (with-meta robj 59 | (binding [*remote-depth* Long/MAX_VALUE] 60 | (proto/-remotify m server))) 61 | robj)))) 62 | 63 | ;; defrecords represent remote wire objects and print as "r/... data" 64 | (defrecord Ref [id]) 65 | (defrecord RVec [id count]) 66 | (defrecord RSet [id count]) 67 | (defrecord RMap [id count]) 68 | (defrecord RSeq [head rest]) 69 | (defrecord RMapEntry [kv]) 70 | (defrecord RFn [id]) 71 | (defrecord RObject [klass ref]) 72 | 73 | (defmethod print-method Ref [rref ^Writer w] 74 | (.write w (str "#r/id ")) 75 | (let [{:keys [id]} rref] 76 | (@#'clojure.core/print id))) 77 | 78 | (defn- record->map 79 | "Convert record into bare map for printing" 80 | [rec] 81 | (into {} rec)) 82 | 83 | (defmethod print-method RVec [rref ^Writer w] 84 | (.write w (str "#r/vec ")) 85 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w)) 86 | 87 | (defmethod print-method RSet [rref ^Writer w] 88 | (.write w (str "#r/set ")) 89 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w)) 90 | 91 | (defmethod print-method RMap [rref ^Writer w] 92 | (.write w (str "#r/map ")) 93 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w)) 94 | 95 | (defmethod print-method RMapEntry [rref ^Writer w] 96 | (.write w (str "#r/kv ")) 97 | (.write w (str (:kv rref)))) 98 | 99 | (defmethod print-method RSeq [rref ^Writer w] 100 | (.write w (str "#r/seq ")) 101 | (let [{:keys [head rest]} rref] 102 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w))) 103 | 104 | (defmethod print-method RFn [rref ^Writer w] 105 | (.write w (str "#r/fn ")) 106 | (let [{:keys [id]} rref] 107 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w))) 108 | 109 | (defmethod print-method RObject [rref ^Writer w] 110 | (.write w (str "#r/object ")) 111 | (@#'clojure.core/print-map (record->map rref) @#'clojure.core/pr-on w)) 112 | 113 | (defn remotify-head 114 | "Remotify the first *depth-length* items in the head of coll" 115 | [server coll] 116 | (binding [*remote-depth* (and *remote-depth* (dec *remote-depth*))] 117 | ;; (println "remotify-head " *remote-lengths* *depth-length*) 118 | (into [] (comp (take *depth-length*) 119 | (map (fn [elem] (remotify elem server)))) 120 | coll))) 121 | 122 | (defn remotify-set 123 | [server coll] 124 | (if (<= (count coll) *depth-length*) 125 | (if (has-remotes? coll) 126 | (if (and *remote-depth* (zero? *remote-depth*)) 127 | (map->Ref {:id (object->rid server coll)}) 128 | (into #{} (remotify-head server coll))) 129 | coll) 130 | (map->RSet (cond-> {:id (object->rid server coll) 131 | :count (count coll)} 132 | (meta coll) (assoc :meta (remotify (meta coll) server)))))) 133 | 134 | (defn remotify-map 135 | [server coll] 136 | ;; (println "remotify-map " *depth-length* " ?= " (count coll) " r? " (has-remotes? coll)) 137 | (if (<= (count coll) *depth-length*) 138 | (if (has-remotes? coll) 139 | (if (and *remote-depth* (zero? *remote-depth*)) 140 | (map->Ref {:id (object->rid server coll)}) 141 | (apply hash-map (interleave (remotify-head server (keys coll)) 142 | (remotify-head server (vals coll))))) 143 | coll) 144 | (map->RMap (cond-> {:id (object->rid server coll) 145 | :count (count coll)} 146 | (meta coll) (assoc :meta (remotify (meta coll) server)))))) 147 | 148 | (defn remotify-vector 149 | [server coll] 150 | ;; (println "remotify-vector " *depth-length* " ?= " (count coll) " r? " (has-remotes? coll)) 151 | (if (<= (count coll) *depth-length*) 152 | (if (has-remotes? coll) 153 | (if (and *remote-depth* (zero? *remote-depth*)) 154 | (map->Ref {:id (object->rid server coll)}) 155 | (into [] (remotify-head server coll))) 156 | coll) 157 | (map->RVec (cond-> {:id (object->rid server coll) 158 | :count (count coll)} 159 | (meta coll) (assoc :meta (remotify (meta coll) server)))))) 160 | 161 | (defn remotify-seq 162 | [server coll] 163 | (if (<= (bounded-count (inc *depth-length*) coll) *depth-length*) 164 | (if (has-remotes? coll) 165 | (seq (remotify-head server coll)) 166 | (map #(remotify % server) coll)) 167 | (map->RSeq (cond-> {:head (remotify-head server coll) 168 | :rest (object->rid server (drop *depth-length* coll))} 169 | (meta coll) (assoc :meta (meta coll)))))) 170 | 171 | (defn remotify-fn 172 | [server f] 173 | (map->RFn {:id (object->rid server f)})) 174 | 175 | (extend-protocol proto/HasRemote 176 | nil (-has-remotes? [_] false) 177 | Boolean (-has-remotes? [_] false) 178 | Object (-has-remotes? [_] true) 179 | String (-has-remotes? [_] false) 180 | Keyword (-has-remotes? [_] false) 181 | Symbol (-has-remotes? [_] false) 182 | Number (-has-remotes? [_] false) 183 | IPersistentCollection 184 | (-has-remotes? 185 | [coll] 186 | ;; (println "has-remotes? " *remote-lengths* *depth-length*) 187 | (or (and *remote-depth* (neg? *remote-depth*)) 188 | (> (bounded-count (inc *depth-length*) coll) *depth-length*) 189 | (transduce 190 | (take *depth-length*) 191 | (completing (fn [result item] 192 | (if (has-remotes? item) 193 | (reduced true) 194 | false))) 195 | false 196 | coll)))) 197 | 198 | (defn- make-robject [obj klass server] 199 | (map->RObject {:klass klass 200 | :ref (map->Ref {:id (object->rid server obj)})})) 201 | 202 | (defn- make-map [obj server] 203 | (cond 204 | (instance? clojure.lang.IRecord obj) (make-robject obj (-> obj class .getName symbol) server) 205 | (instance? IPersistentCollection obj) (remotify-map server obj) 206 | :default (map->Ref {:id (object->rid server obj)}))) 207 | 208 | (extend-protocol proto/Remotify 209 | Object 210 | (-remotify [obj server] 211 | (let [df (d/datafy obj)] 212 | (if (identical? obj df) 213 | (make-robject obj (-> obj class .getName symbol) server) 214 | (remotify df server)))) 215 | 216 | MapEntry 217 | (-remotify 218 | [obj server] 219 | (let [[k v] obj 220 | rk (remotify k server) 221 | rv (remotify v server)] 222 | (->RMapEntry [rk rv]))) 223 | 224 | Associative 225 | (-remotify 226 | [obj server] 227 | (make-map obj server)) 228 | 229 | PersistentArrayMap 230 | (-remotify 231 | [obj server] 232 | (make-map obj server)) 233 | 234 | PersistentHashMap 235 | (-remotify 236 | [obj server] 237 | (make-map obj server)) 238 | 239 | PersistentStructMap 240 | (-remotify 241 | [obj server] 242 | (make-map obj server)) 243 | 244 | PersistentTreeMap 245 | (-remotify 246 | [obj server] 247 | (make-map obj server)) 248 | 249 | PersistentHashSet 250 | (-remotify [coll server] (remotify-set server coll)) 251 | 252 | PersistentTreeSet 253 | (-remotify [coll server] (remotify-set server coll)) 254 | 255 | PersistentVector 256 | (-remotify [coll server] (remotify-vector server coll)) 257 | 258 | ISeq 259 | (-remotify [coll server] (remotify-seq server coll)) 260 | 261 | IFn 262 | (-remotify [f server] (remotify-fn server f)) 263 | 264 | Boolean (-remotify [x _] x) 265 | String (-remotify [x _] x) 266 | Keyword (-remotify [x _] x) 267 | Symbol (-remotify [x _] x) 268 | Number (-remotify [x _] x) 269 | nil (-remotify [x _] nil) 270 | 271 | Ref (-remotify [x _] x) 272 | RVec (-remotify [x _] x) 273 | RSet (-remotify [x _] x) 274 | RMap (-remotify [x _] x) 275 | RSeq (-remotify [x _] x) 276 | RMapEntry (-remotify [x _] x) 277 | RFn (-remotify [x _] x)) 278 | 279 | (comment 280 | (has-remotes? {:a 1}) 281 | 282 | (require 'clojure.data.alpha.replicant.server.impl.cache) 283 | (def C 284 | (let [cache-builder (doto (com.github.benmanes.caffeine.cache.Caffeine/newBuilder) 285 | (.softValues))] 286 | (clojure.data.alpha.replicant.server.impl.cache/create-remote-cache cache-builder))) 287 | 288 | (remotify-seq C (seq {:a 1 :b 2})) 289 | 290 | (do (println :===================================) 291 | (binding [*remote-lengths* [3 1] 292 | *remote-depth* 1] 293 | (remotify [[1 2 3] [4 5 6]] C))) 294 | 295 | (do (println :===================================) 296 | (binding [*remote-lengths* [3 1]] 297 | (has-remotes? [[1 2 3] [4 5 6]]))) 298 | 299 | (do (println :===================================) 300 | (binding [*remote-lengths* [1]] 301 | (remotify {:a {:b {:c 3}} :d 4} C))) 302 | 303 | (do (println :===================================) 304 | (binding [*remote-lengths* [2]] 305 | (remotify {:a {:b {:c 3}}} C))) 306 | 307 | (do (println :===================================) 308 | (map #(binding [*remote-lengths* [3 1]] 309 | (remotify % C)) 310 | [#{1 2 #{3 4 #{5}}} 311 | [1 2 [3 4 [5]]] 312 | {:a {:b {:c 3}}} 313 | [1 2 [3 4]] 314 | [1 2 [3 4] [5 6] [7 8]] 315 | [1 2 [3 4] {5 6} #{7 8}] 316 | [1 2 [(java.util.Date.)]]])) 317 | 318 | (do (println :===================================) 319 | (map #(binding [*remote-lengths* [2]] 320 | (has-remotes? %)) 321 | [#{1 2 #{3 4 #{5}}} 322 | [1 2 [3 4 [5]]] 323 | {:a {:b {:c 3}}} 324 | [1 2 [3 4]] 325 | [1 2 [(java.util.Date.)]]])) 326 | ) 327 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | --------------------------------------------------------------------------------