1
2 """
3
4 Copyright 2009 Glencoe Software, Inc. All rights reserved.
5 Use is subject to license terms supplied in LICENSE.txt
6
7 """
8
9 __save__ = __name__
10 __name__ = 'omero'
11 try:
12 api = __import__('omero.api')
13 model = __import__('omero.model')
14 util = __import__('omero.util')
15 sys = __import__('omero.sys')
16 import omero.all
17 finally:
18 __name__ = __save__
19 del __save__
20
21 sys = __import__("sys")
22 import exceptions, traceback, threading, logging
23 import Ice, Glacier2, Glacier2_Router_ice
24 import uuid
25
27 """
28 Central client-side blitz entry point, and should be in sync with OmeroJava's omero.client
29 and OmeroCpp's omero::client.
30
31 Typical usage includes::
32
33 client = omero.client() # Uses --Ice.Config argument or ICE_CONFIG variable
34 client = omero.client(host = host) # Defines "omero.host"
35 client = omero.client(host = host, port = port) # Defines "omero.host" and "omero.port"
36
37 For more information, see:
38
39 - U{https://trac.openmicroscopy.org.uk/omero/wiki/ClientDesign}
40
41 """
42
43 - def __init__(self, args = None, id = None, \
44 host = None, port = None, pmap = None):
45 """
46 Constructor which takes one sys.argv-style list, one initialization
47 data, one host string, one port integer, and one properties map, in
48 that order. *However*, to simplify use, we reassign values based on
49 their type with a warning printed. A cleaner approach is to use named
50 parameters.
51 ::
52 c1 = omero.client(None, None, "host", myPort) # Correct
53 c2 = omero.client(host = "host", port = myPort) # Correct
54 c3 = omero.client("host", myPort) # Works with warning
55
56 Both "Ice" and "omero" prefixed properties will be parsed.
57
58 Defines the state variables::
59 __previous : InitializationData from any previous communicator, if any
60 Used to re-initialization the client post-closeSession()
61
62 __ic : communicator. Nullness => init() needed on createSession()
63
64 __sf : current session. Nullness => createSession() needed.
65
66 __resources: if non-null, hs access to this client instance and will
67 periodically call sf.keepAlive(None) in order to keep any
68 session alive. This can be enabled either via the omero.keep_alive
69 configuration property, or by calling the enableKeepAlive() method.
70 Once enabled, the period cannot be adjusted during a single
71 session.
72
73 Modifying these variables outside of the accessors can lead to
74 undefined behavior.
75
76 Equivalent to all OmeroJava and OmeroCpp constructors
77 """
78
79
80 self.__agent = "OMERO.py"
81 self.__insecure = False
82 self.__previous = None
83 self.__ic = None
84 self.__oa = None
85 self.__sf = None
86 self.__uuid = None
87 self.__resources = None
88 self.__lock = threading.RLock()
89
90
91 self.__logger = logging.getLogger("omero.client")
92 logging.basicConfig()
93
94
95
96 args, id, host, port, pmap = self._repair(args, id, host, port, pmap)
97
98
99 if not args:
100 args = []
101 else:
102 args = list(args)
103
104
105 if id == None:
106 id = Ice.InitializationData()
107
108 if id.properties == None:
109 id.properties = Ice.createProperties(args)
110
111 id.properties.parseCommandLineOptions("omero", args);
112 if host:
113 id.properties.setProperty("omero.host", str(host))
114 if not port:
115 port = id.properties.getPropertyWithDefault("omero.port",\
116 str(omero.constants.GLACIER2PORT))
117 id.properties.setProperty("omero.port", str(port))
118 if pmap:
119 for k,v in pmap.items():
120 id.properties.setProperty(str(k), str(v))
121
122 self._initData(id)
123
124 - def _repair(self, args, id, host, port, pmap):
125 """
126 Takes the 5 arguments passed to the __init__ method
127 and attempts to re-order them based on their types.
128 This allows for simplified usage without parameter
129 names.
130 """
131 types = [list, Ice.InitializationData, str, int, dict]
132 original = [args, id, host, port, pmap]
133 repaired = [None, None, None, None, None]
134
135
136 valid = True
137 for i in range(0, len(types)):
138 if None != original[i] and not isinstance(original[i], types[i]):
139 valid = False
140 break
141 if valid:
142 return original
143
144
145 for i in range(0, len(types)):
146 found = None
147 for j in range(0, len(types)):
148 if isinstance(original[j], types[i]):
149 if not found:
150 found = original[j]
151 else:
152 raise omero.ClientError("Found two arguments of same type: " + str(types[i]))
153 if found:
154 repaired[i] = found
155 return repaired
156
158 """
159 Initializes the current client via an Ice.InitializationData
160 instance. This is called by all of the constructors, but may
161 also be called on createSession(name, pass) if a previous
162 call to closeSession() has nulled the Ice.Communicator.
163 """
164
165 if not id:
166 raise ClientError("No initialization data provided.");
167
168
169 id.properties.setProperty("Ice.ImplicitContext", "Shared")
170 id.properties.setProperty("Ice.ACM.Client", "0")
171 id.properties.setProperty("Ice.RetryIntervals", "-1")
172 id.properties.setProperty("Ice.Default.EndpointSelection", "Ordered")
173 id.properties.setProperty("Ice.Default.PreferSecure", "1")
174 id.properties.setProperty("Ice.Plugin.IceSSL" , "IceSSL:createIceSSL")
175 id.properties.setProperty("IceSSL.Ciphers" , "ADH")
176 id.properties.setProperty("IceSSL.VerifyPeer" , "0")
177
178
179 messageSize = id.properties.getProperty("Ice.MessageSizeMax")
180 if not messageSize or len(messageSize) == 0:
181 id.properties.setProperty("Ice.MessageSizeMax", str(omero.constants.MESSAGESIZEMAX))
182
183
184 self.parseAndSetInt(id, "Ice.Override.ConnectTimeout",\
185 omero.constants.CONNECTTIMEOUT)
186
187
188 endpoints = id.properties.getProperty("omero.ClientCallback.Endpoints")
189 if not endpoints or len(endpoints) == 0:
190 id.properties.setProperty("omero.ClientCallback.Endpoints", "tcp")
191
192
193 threadpool = id.properties.getProperty("omero.ClientCallback.ThreadPool.Size")
194 if not threadpool or len(threadpool) == 0:
195 id.properties.setProperty("omero.ClientCallback.ThreadPool.Size", str(omero.constants.CLIENTTHREADPOOLSIZE))
196
197
198 port = self.parseAndSetInt(id, "omero.port",\
199 omero.constants.GLACIER2PORT)
200
201
202 router = id.properties.getProperty("Ice.Default.Router")
203 if not router or len(router) == 0:
204 router = str(omero.constants.DEFAULTROUTER)
205 host = id.properties.getPropertyWithDefault("omero.host", """<"omero.host" not set>""")
206 router = router.replace("@omero.port@", str(port))
207 router = router.replace("@omero.host@", str(host))
208 id.properties.setProperty("Ice.Default.Router", router)
209
210
211 dump = id.properties.getProperty("omero.dump")
212 if len(dump) > 0:
213 m = self.getPropertyMap(id.properties)
214 keys = list(m.keys())
215 keys.sort()
216 for key in keys:
217 print "%s=%s" % (key, m[key])
218
219 self.__lock.acquire()
220 try:
221 if self.__ic:
222 raise ClientError("Client already initialized")
223
224 self.__ic = Ice.initialize(id)
225
226 if not self.__ic:
227 raise ClientError("Improper initialization")
228
229
230 self.of = ObjectFactory()
231 self.of.registerObjectFactory(self.__ic)
232 for of in omero.rtypes.ObjectFactories.values():
233 of.register(self.__ic)
234
235
236 self.__uuid = str(uuid.uuid4())
237 ctx = self.__ic.getImplicitContext()
238 if not ctx:
239 raise ClientError("Ice.ImplicitContext not set to Shared")
240 ctx.put(omero.constants.CLIENTUUID, self.__uuid)
241
242
243 self.__oa = self.__ic.createObjectAdapter("omero.ClientCallback")
244 cb = BaseClient.CallbackI(self.__ic, self.__oa)
245 self.__oa.add(cb, self.__ic.stringToIdentity("ClientCallback/%s" % self.__uuid))
246 self.__oa.activate()
247 finally:
248 self.__lock.release()
249
251 """
252 Sets the omero.model.Session#getUserAgent() string for
253 this client. Every session creation will be passed this argument. Finding
254 open sesssions with the same agent can be done via
255 omero.api.ISessionPrx#getMyOpenAgentSessions(String).
256 """
257 self.__agent = agent
258
260 """
261 Specifies whether or not this client was created via a call to
262 createClient with a boolean of False. If insecure, then all
263 remote calls will use the insecure connection defined by the server.
264 """
265 return not self.__insecure
266
268 """
269 Creates a possibly insecure omero.client instance and calls joinSession
270 using the current getSessionId value. If secure is False, then first the
271 "omero.router.insecure" configuration property is retrieved from the server
272 and used as the value of "Ice.Default.Router" for the new client. Any exception
273 thrown during creation is passed on to the caller.
274 """
275 props = self.getPropertyMap()
276 if not secure:
277 insecure = self.getSession().getConfigService().getConfigValue("omero.router.insecure")
278 if insecure is not None and insecure != "":
279 props["Ice.Default.Router"] = insecure
280 else:
281 self.__logger.warn("Could not retrieve \"omero.router.insecure\"")
282
283 nClient = omero.client(props)
284 nClient.__insecure = not secure
285 nClient.setAgent("%s;secure=%s" % (self.__agent, secure))
286 nClient.joinSession(self.getSessionId())
287 return nClient
288
290 """
291 Calls closeSession() and ignores any exceptions.
292
293 Equivalent to close() in OmeroJava or omero::client::~client()
294 """
295 try:
296 self.closeSession()
297 except exceptions.Exception, e:
298 self.__logger.warning("Ignoring error in client.__del__:" + str(e.__class__))
299
301 """
302 Returns the Ice.Communicator for this instance or throws
303 an exception if None.
304 """
305 self.__lock.acquire()
306 try:
307 if not self.__ic:
308 raise ClientError("No Ice.Communicator active; call createSession() or create a new client instance")
309 return self.__ic
310 finally:
311 self.__lock.release()
312
314 """
315 Returns the Ice.ObjectAdapter for this instance or throws
316 an exception if None.
317 """
318 self.__lock.acquire()
319 try:
320 if not self.__oa:
321 raise ClientError("No Ice.ObjectAdapter active; call createSession() or create a new client instance")
322 return self.__oa
323 finally:
324 self.__lock.release()
325
327 """
328 Returns the current active session or throws an exception if none has been
329 created since the last closeSession()
330 """
331 self.__lock.acquire()
332 try:
333 sf = self.__sf
334 if not sf:
335 raise omero.ClientError("No session avaliable")
336 return sf
337 finally:
338 self.__lock.release()
339
341 """
342 Returns the UUID for the current session without making a remote call.
343 Uses getSession() internally and will throw an exception if no session
344 is active.
345 """
346 return self.getSession().ice_getIdentity().name
347
349 """
350 Returns the Ice.ImplicitContext which defines what properties
351 will be sent on every method invocation.
352 """
353 return self.getCommunicator().getImplicitContext()
354
356 """
357 Returns the active properties for this instance
358 """
359 self.__lock.acquire()
360 try:
361 return self.__ic.getProperties()
362 finally:
363 self.__lock.release()
364
370
372 """
373 Returns all properties which are prefixed with "omero." or "Ice."
374 """
375 if properties is None:
376 properties = self.getProperties()
377
378 rv = dict()
379 for prefix in ["omero","Ice"]:
380 for k,v in properties.getPropertiesForPrefix(prefix).items():
381 rv[k] = v
382 return rv
383
385 """
386 Uses the given session uuid as name
387 and password to rejoin a running session
388 """
389 return self.createSession(session, session)
390
392 """
393 Performs the actual logic of logging in, which is done via the
394 getRouter(). Disallows an extant ServiceFactoryPrx, and
395 tries to re-create a null Ice.Communicator. A null or empty
396 username will throw an exception, but an empty password is allowed.
397 """
398 import omero
399
400 self.__lock.acquire()
401 try:
402
403
404
405 if self.__sf:
406 raise ClientError("Session already active. Create a new omero.client or closeSession()")
407
408 if not self.__ic:
409 if not self.__previous:
410 raise ClientError("No previous data to recreate communicator.")
411 self._initData(self.__previous)
412 self.__previous = None
413
414
415
416 if not username:
417 username = self.getProperty("omero.user")
418 elif isinstance(username,omero.RString):
419 username = username.val
420
421 if not username or len(username) == 0:
422 raise ClientError("No username specified")
423
424 if not password:
425 password = self.getProperty("omero.pass")
426 elif isinstance(password,omero.RString):
427 password = password.val
428
429 if not password:
430 raise ClientError("No password specified")
431
432
433 prx = None
434 retries = 0
435 while retries < 3:
436 reason = None
437 if retries > 0:
438 self.__logger.warning(\
439 "%s - createSession retry: %s"% (reason, retries) )
440 try:
441 ctx = dict(self.getImplicitContext().getContext())
442 ctx[omero.constants.AGENT] = self.__agent
443 prx = self.getRouter(self.__ic).createSession(username, password, ctx)
444 break
445 except omero.WrappedCreateSessionException, wrapped:
446 if not wrapped.concurrency:
447 raise wrapped
448 reason = "%s:%s" % (wrapped.type, wrapped.reason)
449 retries = retries + 1
450 except Ice.ConnectTimeoutException, cte:
451 reason = "Ice.ConnectTimeoutException:%s" % str(cte)
452 retries = retries + 1
453
454 if not prx:
455 raise ClientError("Obtained null object prox")
456
457
458 self.__sf = omero.api.ServiceFactoryPrx.uncheckedCast(prx)
459 if not self.__sf:
460 raise ClientError("Obtained object proxy is not a ServiceFactory")
461
462
463 keep_alive = self.__ic.getProperties().getPropertyWithDefault("omero.keep_alive", "-1")
464 try:
465 i = int(keep_alive)
466 self.enableKeepAlive(i)
467 except:
468 pass
469
470
471
472 try:
473 id = self.__ic.stringToIdentity("ClientCallback/%s" % self.__uuid )
474 raw = self.__oa.createProxy(id)
475 self.__sf.setCallback(omero.api.ClientCallbackPrx.uncheckedCast(raw))
476
477 except:
478 self.__del__()
479 raise
480
481
482 self.getImplicitContext().put(omero.constants.SESSIONUUID, self.getSessionId())
483
484 return self.__sf
485 finally:
486 self.__lock.release()
487
489 """
490 Resets the "omero.keep_alive" property on the current
491 Ice.Communicator which is used on initialization to determine
492 the time-period between Resource checks. If no __resources
493 instance is available currently, one is also created.
494 """
495
496 self.__lock.acquire()
497 try:
498
499 ic = self.getCommunicator()
500
501
502
503 ic.getProperties().setProperty("omero.keep_alive", str(seconds))
504
505
506
507 if self.__resources == None and seconds > 0:
508 self.__resources = omero.util.Resources(seconds)
509 class Entry:
510 def __init__(self, c):
511 self.c = c
512 def cleanup(self): pass
513 def check(self):
514 sf = self.c._BaseClient__sf
515 ic = self.c._BaseClient__ic
516 if sf != None:
517 try:
518 sf.keepAlive(None)
519 except exceptions.Exception, e:
520 if ic != None:
521 ic.getLogger().warning("Proxy keep alive failed.")
522 return False
523 return True
524 self.__resources.add(Entry(self))
525 finally:
526 self.__lock.release()
527
529 """
530 Acquires the default router, and throws an exception
531 if it is not of type Glacier2.Router. Also sets the
532 Ice.ImplicitContext on the router proxy.
533 """
534 prx = comm.getDefaultRouter()
535 if not prx:
536 raise ClientError("No default router found.")
537 router = Glacier2.RouterPrx.uncheckedCast(prx)
538 if not router:
539 raise ClientError("Error obtaining Glacier2 router")
540
541
542
543 router = router.ice_context(comm.getImplicitContext().getContext())
544 return router
545
546 - def sha1(self, filename):
547 """
548 Calculates the local sha1 for a file.
549 """
550 try:
551 from hashlib import sha1 as sha_new
552 except ImportError:
553 from sha import new as sha_new
554 digest = sha_new()
555 file = open(filename, 'rb')
556 try:
557 while True:
558 block = file.read(1024)
559 if not block:
560 break
561 digest.update(block)
562 finally:
563 file.close()
564 return digest.hexdigest()
565
566 - def upload(self, filename, name = None, path = None,
567 type = None, ofile = None, block_size = 1024):
568 """
569 Utility method to upload a file to the server.
570 """
571 if not self.__sf:
572 raise ClientError("No session. Use createSession first.")
573
574 import os, types
575 if not filename or not isinstance(filename, types.StringType):
576 raise ClientError("Non-null filename must be provided")
577
578 if not os.path.exists(filename):
579 raise ClientError("File does not exist: " + filename)
580
581 from path import path as __path__
582 filepath = __path__(filename)
583 file = open(filename, 'rb')
584 try:
585
586 size = os.path.getsize(file.name)
587 if block_size > size:
588 block_size = size
589
590 if not ofile:
591 ofile = omero.model.OriginalFileI()
592
593 ofile.size = omero.rtypes.rlong(size)
594 ofile.sha1 = omero.rtypes.rstring(self.sha1(file.name))
595
596 abspath = filepath.normpath().abspath()
597 if not ofile.name:
598 if name:
599 ofile.name = omero.rtypes.rstring(name)
600 else:
601 ofile.name = omero.rtypes.rstring(str(abspath.basename()))
602
603 if not ofile.path:
604 ofile.path = omero.rtypes.rstring(str(abspath.dirname())+os.path.sep)
605
606 if not ofile.mimetype:
607 if type:
608
609 ofile.mimetype = omero.rtypes.rstring(type)
610
611
612
613
614
615 up = self.__sf.getUpdateService()
616 ofile = up.saveAndReturnObject(ofile)
617
618 prx = self.__sf.createRawFileStore()
619 try:
620 prx.setFileId(ofile.id.val)
621 prx.truncate(size)
622 offset = 0
623 while True:
624 block = file.read(block_size)
625 if not block:
626 break
627 prx.write(block, offset, len(block))
628 offset += len(block)
629 finally:
630 prx.close()
631 finally:
632 file.close()
633
634 return ofile
635
636 - def download(self, ofile, filename = None, block_size = 1024*1024, filehandle = None):
637 prx = self.__sf.createRawFileStore()
638 try:
639 if not ofile or not ofile.id:
640 raise ClientError("No file to download")
641 ofile = self.__sf.getQueryService().get("OriginalFile", ofile.id.val)
642
643 if block_size > ofile.size.val:
644 block_size = ofile.size.val
645
646 prx.setFileId(ofile.id.val)
647
648 size = ofile.size.val
649 offset = 0
650
651 if filehandle is None:
652 if filename is None:
653 raise ClientError("no filename or filehandle specified")
654 filehandle = open(filename, 'wb')
655 else:
656 if filename:
657 raise ClientError("filename and filehandle specified.")
658
659 try:
660 while (offset+block_size) < size:
661 filehandle.write(prx.read(offset, block_size))
662 offset += block_size
663 filehandle.write(prx.read(offset, (size-offset)))
664 finally:
665 filehandle.close()
666 finally:
667 prx.close()
668
670 """
671 Closes the Router connection created by createSession(). Due to a bug in Ice,
672 only one connection is allowed per communicator, so we also destroy the communicator.
673 """
674
675 self.__lock.acquire()
676 try:
677 self.__sf = None
678
679 oldOa = self.__oa
680 self.__oa = None
681
682 oldIc = self.__ic
683 self.__ic = None
684
685
686 if not oldIc:
687 return
688
689 if oldOa:
690 try:
691 oldOa.deactivate()
692 except exceptions.Exception, e:
693 self.__logger.warning("While deactivating adapter: " + str(e.message))
694
695 self.__previous = Ice.InitializationData()
696 self.__previous.properties = oldIc.getProperties().clone()
697
698 oldR = self.__resources
699 self.__resources = None
700 if oldR != None:
701 try:
702 oldR.cleanup()
703 except exceptions.Exception, e:
704 oldIc.getLogger().warning(
705 "While cleaning up resources: " + str(e))
706
707 try:
708 try:
709 self.getRouter(oldIc).destroySession()
710 except Glacier2.SessionNotExistException:
711
712 pass
713 except Ice.ConnectionLostException:
714
715 pass
716 except Ice.ConnectionRefusedException:
717
718 pass
719 except Ice.ConnectTimeoutException:
720
721 pass
722
723
724 finally:
725 oldIc.destroy()
726 del oldIc._impl
727
728 finally:
729 self.__lock.release()
730
731
733 """
734 Calls ISession.closeSession(omero.model.Session) until
735 the returned reference count is greater than zero. The
736 number of invocations is returned. If ISession.closeSession()
737 cannot be called, -1 is returned.
738 """
739
740 s = omero.model.SessionI()
741 s.uuid = omero.rtypes.rstring(self.getSessionId())
742 try:
743 svc = self.sf.getSessionService()
744 except:
745 self.__logger.warning("Cannot get session service for killSession. Using closeSession")
746 self.closeSession()
747 return -1;
748
749 count = 0
750 try:
751 r = 1
752 while r > 0:
753 count += 1
754 r = svc.closeSession(s)
755 except omero.RemovedSessionException:
756 pass
757 except:
758 self.__logger.warning("Unknown exception while closing all references", exc_info = True)
759
760
761 self.closeSession()
762 return count
763
764
765
766
767 - def _env(self, _unwrap, method, *args):
768 """ Helper method to access session environment"""
769 session = self.getSession()
770 if not session:
771 raise ClientError("No session active")
772 a = session.getAdminService()
773 u = a.getEventContext().sessionUuid
774 s = session.getSessionService()
775 m = getattr(s, method)
776 rv = apply(m, (u,)+args)
777 if callable(_unwrap):
778 rv = _unwrap(rv)
779 elif _unwrap:
780 rv = omero.rtypes.unwrap(rv)
781 return rv
782
788
790 """
791 Retrieves an item from the "output" shared (session) memory.
792 """
793 return self._env(unwrap, "getOutput", key)
794
795
801
803 """
804 Sets an item in the "output" shared (session) memory under the given name.
805 """
806 self._env(False, "setOutput", key, value)
807
813
815 """
816 Returns a list of keys for all items in the "output" shared (session) memory
817 """
818 return self._env(False, "getOutputKeys")
819
825
827 """
828 Returns all items in the "output" shared (session) memory
829 """
830 return self._env(unwrap, "getOutputKeys")
831
832
833
834
836 currentValue = data.properties.getProperty(key)
837 if not currentValue or len(currentValue) == 0:
838 newStr = str(newValue)
839 data.properties.setProperty(key, newStr)
840 currentValue = newStr
841 return currentValue
842
844 """
845 Compatibility layer, which allows calls to getCommunicator() and getSession()
846 to be called via self.ic and self.sf
847 """
848 if name == "ic":
849 return self.getCommunicator()
850 elif name == "sf":
851 return self.getSession()
852 elif name == "adapter":
853 return self.getAdapter()
854 else:
855 raise AttributeError("Unknown property: " + name)
856
857
858
859
861 if not self.__oa:
862 raise ClientError("No session active; call createSession()")
863 obj = self.__oa.find(self.ic.stringToIdentity("ClientCallback/%s" % self.__uuid))
864 if not isinstance(obj, BaseClient.CallbackI):
865 raise ClientError("Cannot find CallbackI in ObjectAdapter")
866 return obj
867
870
873
876
878 """
879 Implemention of ClientCallback which will be added to
880 any Session which this instance creates. Note: this client
881 should avoid all interaction with the {@link client#lock} since it
882 can lead to deadlocks during shutdown. See: ticket:1210
883 """
884
885
886
887
891 try:
892 self.oa.deactivate();
893 except exceptions.Exception, e:
894 sys.err.write("On session closed: " + str(e))
895
902 - def execute(self, myCallable, action):
903 try:
904 myCallable()
905
906 except:
907 try:
908 self.ic.getLogger().error("Error performing %s" % action)
909 except:
910 print "Error performing %s" % action
911
914 - def shutdownIn(self, milliseconds, current = None):
918
919
920
921
922
923 import util.FactoryMap
925 """
926 Responsible for instantiating objects during deserialization.
927 """
928
931
933 for key in self.__m:
934 if not ic.findObjectFactory(key):
935 ic.addObjectFactory(self,key)
936
938 generator = self.__m[type]
939 if generator == None:
940 raise ClientError("Unknown type:"+type)
941 return generator.next()
942
946
947
948
950 """
951 Top of client exception hierarchy.
952 """
953 pass
954
957
960