├── .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 |
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 | --------------------------------------------------------------------------------