-
Notifications
You must be signed in to change notification settings - Fork 4
/
init.clj
280 lines (243 loc) · 11.4 KB
/
init.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
(ns clj-jargon.init
(:require [clojure.tools.logging :as log]
[otel.otel :as otel]
[slingshot.slingshot :refer [try+ throw+]]
[clojure-commons.error-codes :refer [ERR_NOT_A_USER]])
(:import [java.io InputStream]
[java.net ConnectException]
[org.irods.jargon.core.connection IRODSAccount]
[org.irods.jargon.core.exception InvalidClientUserException]
[org.irods.jargon.core.pub IRODSAccessObjectFactory ; Hint for Cursive inspection
IRODSFileSystem]
[org.irods.jargon.ticket TicketServiceFactoryImpl]))
; Debuging code.
(def with-jargon-index (ref 0))
(def ^:dynamic curr-with-jargon-index nil)
(defn clean-return
[cm retval]
(log/debug curr-with-jargon-index "- cleaning up and returning a plain value")
(when (and (delay? cm) (realized? cm)) (.close (:proxy @cm)))
(when-not (delay? cm) (.close (:proxy cm)))
retval)
(defn dirty-return
[retval]
(log/debug curr-with-jargon-index "- returning without cleaning up...")
retval)
(defn proxy-input-stream
[cm ^InputStream istream]
(let [with-jargon-index curr-with-jargon-index
cm-proxy (if (delay? cm) (:proxy @cm) (:proxy cm))]
(proxy [InputStream] []
(available [] (.available istream))
(mark [readlimit] (.mark istream readlimit))
(markSupported [] (.markSupported istream))
(read
([] (otel/with-span [_ ["proxy-input-stream read (1 byte)" {:kind :client}]] (.read istream)))
([b] (otel/with-span [_ ["proxy-input-stream read (buffer)" {:kind :client}]] (.read istream b)))
([b off len] (otel/with-span [_ ["proxy-input-stream read (buf, offset, len)" {:kind :client}]] (.read istream b off len))))
(reset [] (.reset istream))
(skip [n] (.skip istream n))
(close []
(log/debug with-jargon-index "- closing the proxy input stream")
(.close istream)
(.close cm-proxy)))))
(defn proxy-input-stream-return
[cm retval]
(log/debug curr-with-jargon-index "- returning a proxy input stream...")
(proxy-input-stream cm retval))
(defn override-user-account
[cfg user pass]
(IRODSAccount. (:host cfg)
(Integer/parseInt (:port cfg))
user
pass
(:home cfg)
(:zone cfg)
(:defaultResource cfg)))
(defn anonymous-user-account
[cfg]
(IRODSAccount/instanceForAnonymous (:host cfg)
(Integer/parseInt (:port cfg))
(:home cfg)
(:zone cfg)
(:defaultResource cfg)))
(defn- account
[cfg client-user]
(if (= (:username cfg) "anonymous")
(anonymous-user-account cfg)
(IRODSAccount/instanceWithProxy (:host cfg)
(Integer/parseInt (:port cfg))
client-user
(:password cfg)
(:home cfg)
(:zone cfg)
(:defaultResource cfg)
(:username cfg)
(:zone cfg))))
(defn- context-map
"Throws:
org.irods.jargon.core.exception.JargonException - This is thrown when if fails to connect to iRODS
ERR_NOT_A_USER - If the client-user isn't a known iRODS user"
[{^IRODSFileSystem cm-proxy :proxy :as cfg} client-user]
(try+
(let [aof (.getIRODSAccessObjectFactory cm-proxy)
acnt (.getAuthenticatedIRODSAccount (.authenticateIRODSAccount aof (account cfg client-user)))]
(assoc cfg
:irodsAccount acnt
:accessObjectFactory aof
:ticketServiceFactory (TicketServiceFactoryImpl. aof)
:collectionAO (.getCollectionAO aof acnt)
:dataObjectAO (.getDataObjectAO aof acnt)
:userAO (.getUserAO aof acnt)
:userGroupAO (.getUserGroupAO aof acnt)
:fileFactory (.getIRODSFileFactory cm-proxy acnt)
:fileSystemAO (.getIRODSFileSystemAO aof acnt)
:lister (.getCollectionAndDataObjectListAndSearchAO aof acnt)
:quotaAO (.getQuotaAO aof acnt)
:executor (.getIRODSGenQueryExecutor aof acnt)))
(catch InvalidClientUserException _
(throw+ {:error_code ERR_NOT_A_USER :user client-user}))))
(defn- log-value
[msg value]
(log/debug curr-with-jargon-index "-" msg value)
value)
(defn- get-context
"Throws:
org.irods.jargon.core.exception.JargonException - This is thrown when if fails to connect to iRODS"
[cfg client-user]
(let [retval {:succeeded true :retval nil :exception nil :retry false}]
(try
(log-value "retval:" (assoc retval :retval (context-map cfg client-user)))
(catch ConnectException e
(log/debug curr-with-jargon-index "- caught a ConnectException:" e)
(log/debug curr-with-jargon-index "- need to retry...")
(assoc retval :exception e :succeeded false :retry true))
(catch Exception e
(log/debug curr-with-jargon-index "- got an Exception:" e)
(log/debug curr-with-jargon-index "- shouldn't retry...")
(assoc retval :exception e :succeeded false :retry false)))))
(defn create-jargon-context-map
"Creates a map containing instances of commonly used Jargon objects.
Throws:
org.irods.jargon.core.exception.JargonException - This is thrown when if fails to connect to iRODS"
[cfg client-user]
(otel/with-span [s ["create-jargon-context-map" {:kind :internal}]]
(loop [num-tries 0]
(let [retval (get-context cfg client-user)
error? (not (:succeeded retval))
retry? (:retry retval)]
(cond
(and error? retry? (< num-tries (:max-retries cfg)))
(do (Thread/sleep (:retry-sleep cfg))
(recur (inc num-tries)))
error?
(throw (:exception retval))
:else (:retval retval))))))
(defn create-context-map-or-delay
"Creates a context map or a delay for one, depending whether lazy? is true"
[cfg client-user lazy?]
(if lazy?
(delay (create-jargon-context-map cfg client-user))
(create-jargon-context-map cfg client-user)))
(defmacro with-jargon
"An iRODS connection is opened, binding the connection's context to the symbolic cm-sym value.
Next it evaluates the body expressions. Finally, it closes the iRODS connection*. The body
expressions should use the value of cm-sym to access the iRODS context.
Calling:
(with-jargon cfg [cm-sym] body)
(with-jargon cfg :opt-k opt-v ... [cm-sym] body)
Parameters:
cfg - The Jargon configuration used to connect to iRODS.
[cm-sym] - Holds the name of the binding to the iRODS context map used by the body expressions.
body - Zero or more expressions to be evaluated while an iRODS connection is open.
Options:
:auto-close - true if the connection should be closed automatically (default: true)
:client-user - the user to operate as inside of iRODS (default: (:username cfg))
:lazy - true if the cm-sym should be a delay to be dereferenced as needed (default: false)
Returns:
It returns the result from evaluating the last expression in the body.*
Throws:
org.irods.jargon.core.exception.JargonException - This is thrown when if fails to connect to
iRODS.
Example:
(def config (init ...))
(with-jargon config [ctx]
(list-all ctx \"/zone/home/user/\"))
* If an IRODSFileInputStream is the result of the last body expression, the iRODS connection is
not closed. Instead, a special InputStream is returned that when closed, closes the iRODS
connection as well. If the auto-close option is set to false (it's set to true by default)
then the connection is not closed automatically. In that case, the caller must take steps to
ensure that the connection will be closed (for example, by including a proxy input stream
somewhere in the result and calling the close method on that proxy input stream later)."
[cfg & params]
(let [[opts-map [[cm-sym] & body]] (split-with #(not (vector? %)) params)
opts (apply hash-map opts-map)]
`(let [auto-close# (if (nil? (:auto-close ~opts)) true (:auto-close ~opts))
client-user# (if (:client-user ~opts) (:client-user ~opts) (:username ~cfg))
lazy# (if (nil? (:lazy ~opts)) false (:lazy ~opts))]
(binding [curr-with-jargon-index (dosync (alter with-jargon-index inc))]
(log/debug "curr-with-jargon-index:" curr-with-jargon-index)
(when-let [~cm-sym (create-context-map-or-delay ~cfg client-user# lazy#)]
(try+
(let [retval# (do ~@body)]
(cond
(instance? InputStream retval#) (proxy-input-stream-return ~cm-sym retval#)
auto-close# (clean-return ~cm-sym retval#)
:else (dirty-return retval#)))
(catch Object o1#
(try+
(clean-return ~cm-sym nil) ;use clean-return which knows how to handle delays and never-actually-used contexts
(catch Object o2#))
(throw+))))))))
(defmacro log-stack-trace
[msg]
`(log/warn (Exception. "forcing a stack trace") ~msg))
(def ^IRODSFileSystem default-proxy-ctor
"This is the default constructor for creating an iRODS proxy."
#(IRODSFileSystem/instance))
(defn init
"Creates the iRODS configuration map.
Parameters:
host - The IP address or FQDN of the iRODS server that will be used.
port - The IP port the iRODS server listens to.
username - The iRODS user name of the account that will be used while
connected to iRODS.
password - The password of user.
home - The path to the user's home collection.
zone - The zone to use
defaultResource - The default resource to use.
max-retries - The number of times to retry connecting to the server. This
defaults to 0.
retry-sleep - The number of milliseconds to wait between connection
retries. This defaults to 0.
use-trash - Indicates whether or to put deleted entries in the trash.
This defaults to false.
proxy-ctor - This is the constructor to use for creating the iRODS proxy.
It takes no arguments, and the object its creates must implement have
the following methods.
((close [_])
(^IRODSAccessObjectFactory getIRODSAccessObjectFactory [_])
(^IRODSFileFactory getIRODSFileFactory [_ ^IRODSAccount acnt]))
These must be sematically equivalent to the corresponding methods in
org.irods.jargon.core.pub.IRODSFileSystem. This argument defaults to
default-proxy-ctor.
Returns:
A map is returned with the provided parameters names and values forming
the key-value pairs."
[host port user pass home zone res
& {:keys [max-retries retry-sleep use-trash proxy-ctor]
:or {max-retries 0
retry-sleep 0
use-trash false
proxy-ctor default-proxy-ctor}}]
{:host host
:port port
:username user
:password pass
:home home
:zone zone
:defaultResource res
:max-retries max-retries
:retry-sleep retry-sleep
:use-trash use-trash
:proxy (proxy-ctor)})