Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / spread / flavors.py
1 # -*- test-case-name: twisted.test.test_pb -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 This module represents flavors of remotely acessible objects.
7
8 Currently this is only objects accessible through Perspective Broker, but will
9 hopefully encompass all forms of remote access which can emulate subsets of PB
10 (such as XMLRPC or SOAP).
11
12 Future Plans: Optimization.  Exploitation of new-style object model.
13 Optimizations to this module should not affect external-use semantics at all,
14 but may have a small impact on users who subclass and override methods.
15
16 @author: Glyph Lefkowitz
17 """
18
19 # NOTE: this module should NOT import pb; it is supposed to be a module which
20 # abstractly defines remotely accessible types.  Many of these types expect to
21 # be serialized by Jelly, but they ought to be accessible through other
22 # mechanisms (like XMLRPC)
23
24 # system imports
25 import sys
26 from zope.interface import implements, Interface
27
28 # twisted imports
29 from twisted.python import log, reflect
30
31 # sibling imports
32 from jelly import setUnjellyableForClass, setUnjellyableForClassTree, setUnjellyableFactoryForClass, unjellyableRegistry
33 from jelly import Jellyable, Unjellyable, _newDummyLike
34 from jelly import setInstanceState, getInstanceState
35
36 # compatibility
37 setCopierForClass = setUnjellyableForClass
38 setCopierForClassTree = setUnjellyableForClassTree
39 setFactoryForClass = setUnjellyableFactoryForClass
40 copyTags = unjellyableRegistry
41
42 copy_atom = "copy"
43 cache_atom = "cache"
44 cached_atom = "cached"
45 remote_atom = "remote"
46
47
48 class NoSuchMethod(AttributeError):
49     """Raised if there is no such remote method"""
50
51
52 class IPBRoot(Interface):
53     """Factory for root Referenceable objects for PB servers."""
54
55     def rootObject(broker):
56         """Return root Referenceable for broker."""
57
58
59 class Serializable(Jellyable):
60     """An object that can be passed remotely.
61
62     I am a style of object which can be serialized by Perspective
63     Broker.  Objects which wish to be referenceable or copied remotely
64     have to subclass Serializable.  However, clients of Perspective
65     Broker will probably not want to directly subclass Serializable; the
66     Flavors of transferable objects are listed below.
67
68     What it means to be \"Serializable\" is that an object can be
69     passed to or returned from a remote method.  Certain basic types
70     (dictionaries, lists, tuples, numbers, strings) are serializable by
71     default; however, classes need to choose a specific serialization
72     style: L{Referenceable}, L{Viewable}, L{Copyable} or L{Cacheable}.
73
74     You may also pass C{[lists, dictionaries, tuples]} of L{Serializable}
75     instances to or return them from remote methods, as many levels deep
76     as you like.
77     """
78
79     def processUniqueID(self):
80         """Return an ID which uniquely represents this object for this process.
81
82         By default, this uses the 'id' builtin, but can be overridden to
83         indicate that two values are identity-equivalent (such as proxies
84         for the same object).
85         """
86
87         return id(self)
88
89 class Referenceable(Serializable):
90     perspective = None
91     """I am an object sent remotely as a direct reference.
92
93     When one of my subclasses is sent as an argument to or returned
94     from a remote method call, I will be serialized by default as a
95     direct reference.
96
97     This means that the peer will be able to call methods on me;
98     a method call xxx() from my peer will be resolved to methods
99     of the name remote_xxx.
100     """
101
102     def remoteMessageReceived(self, broker, message, args, kw):
103         """A remote message has been received.  Dispatch it appropriately.
104
105         The default implementation is to dispatch to a method called
106         'remote_messagename' and call it with the same arguments.
107         """
108         args = broker.unserialize(args)
109         kw = broker.unserialize(kw)
110         method = getattr(self, "remote_%s" % message, None)
111         if method is None:
112             raise NoSuchMethod("No such method: remote_%s" % (message,))
113         try:
114             state = method(*args, **kw)
115         except TypeError:
116             log.msg("%s didn't accept %s and %s" % (method, args, kw))
117             raise
118         return broker.serialize(state, self.perspective)
119
120     def jellyFor(self, jellier):
121         """(internal)
122
123         Return a tuple which will be used as the s-expression to
124         serialize this to a peer.
125         """
126
127         return ["remote", jellier.invoker.registerReference(self)]
128
129
130 class Root(Referenceable):
131     """I provide a root object to L{pb.Broker}s for a L{pb.BrokerFactory}.
132
133     When a L{pb.BrokerFactory} produces a L{pb.Broker}, it supplies that
134     L{pb.Broker} with an object named \"root\".  That object is obtained
135     by calling my rootObject method.
136     """
137
138     implements(IPBRoot)
139     
140     def rootObject(self, broker):
141         """A L{pb.BrokerFactory} is requesting to publish me as a root object.
142
143         When a L{pb.BrokerFactory} is sending me as the root object, this
144         method will be invoked to allow per-broker versions of an
145         object.  By default I return myself.
146         """
147         return self
148
149
150 class ViewPoint(Referenceable):
151     """
152     I act as an indirect reference to an object accessed through a
153     L{pb.Perspective}.
154
155     Simply put, I combine an object with a perspective so that when a
156     peer calls methods on the object I refer to, the method will be
157     invoked with that perspective as a first argument, so that it can
158     know who is calling it.
159
160     While L{Viewable} objects will be converted to ViewPoints by default
161     when they are returned from or sent as arguments to a remote
162     method, any object may be manually proxied as well. (XXX: Now that
163     this class is no longer named C{Proxy}, this is the only occourance
164     of the term 'proxied' in this docstring, and may be unclear.)
165
166     This can be useful when dealing with L{pb.Perspective}s, L{Copyable}s,
167     and L{Cacheable}s.  It is legal to implement a method as such on
168     a perspective::
169
170      | def perspective_getViewPointForOther(self, name):
171      |     defr = self.service.getPerspectiveRequest(name)
172      |     defr.addCallbacks(lambda x, self=self: ViewPoint(self, x), log.msg)
173      |     return defr
174
175     This will allow you to have references to Perspective objects in two
176     different ways.  One is through the initial 'attach' call -- each
177     peer will have a L{pb.RemoteReference} to their perspective directly.  The
178     other is through this method; each peer can get a L{pb.RemoteReference} to
179     all other perspectives in the service; but that L{pb.RemoteReference} will
180     be to a L{ViewPoint}, not directly to the object.
181
182     The practical offshoot of this is that you can implement 2 varieties
183     of remotely callable methods on this Perspective; view_xxx and
184     C{perspective_xxx}. C{view_xxx} methods will follow the rules for
185     ViewPoint methods (see ViewPoint.L{remoteMessageReceived}), and
186     C{perspective_xxx} methods will follow the rules for Perspective
187     methods.
188     """
189
190     def __init__(self, perspective, object):
191         """Initialize me with a Perspective and an Object.
192         """
193         self.perspective = perspective
194         self.object = object
195
196     def processUniqueID(self):
197         """Return an ID unique to a proxy for this perspective+object combination.
198         """
199         return (id(self.perspective), id(self.object))
200
201     def remoteMessageReceived(self, broker, message, args, kw):
202         """A remote message has been received.  Dispatch it appropriately.
203
204         The default implementation is to dispatch to a method called
205         'C{view_messagename}' to my Object and call it on my object with
206         the same arguments, modified by inserting my Perspective as
207         the first argument.
208         """
209         args = broker.unserialize(args, self.perspective)
210         kw = broker.unserialize(kw, self.perspective)
211         method = getattr(self.object, "view_%s" % message)
212         try:
213             state = apply(method, (self.perspective,)+args, kw)
214         except TypeError:
215             log.msg("%s didn't accept %s and %s" % (method, args, kw))
216             raise
217         rv = broker.serialize(state, self.perspective, method, args, kw)
218         return rv
219
220
221 class Viewable(Serializable):
222     """I will be converted to a L{ViewPoint} when passed to or returned from a remote method.
223
224     The beginning of a peer's interaction with a PB Service is always
225     through a perspective.  However, if a C{perspective_xxx} method returns
226     a Viewable, it will be serialized to the peer as a response to that
227     method.
228     """
229
230     def jellyFor(self, jellier):
231         """Serialize a L{ViewPoint} for me and the perspective of the given broker.
232         """
233         return ViewPoint(jellier.invoker.serializingPerspective, self).jellyFor(jellier)
234
235
236
237 class Copyable(Serializable):
238     """Subclass me to get copied each time you are returned from or passed to a remote method.
239
240     When I am returned from or passed to a remote method call, I will be
241     converted into data via a set of callbacks (see my methods for more
242     info).  That data will then be serialized using Jelly, and sent to
243     the peer.
244
245     The peer will then look up the type to represent this with; see
246     L{RemoteCopy} for details.
247     """
248
249     def getStateToCopy(self):
250         """Gather state to send when I am serialized for a peer.
251
252         I will default to returning self.__dict__.  Override this to
253         customize this behavior.
254         """
255
256         return self.__dict__
257
258     def getStateToCopyFor(self, perspective):
259         """
260         Gather state to send when I am serialized for a particular
261         perspective.
262
263         I will default to calling L{getStateToCopy}.  Override this to
264         customize this behavior.
265         """
266
267         return self.getStateToCopy()
268
269     def getTypeToCopy(self):
270         """Determine what type tag to send for me.
271
272         By default, send the string representation of my class
273         (package.module.Class); normally this is adequate, but
274         you may override this to change it.
275         """
276
277         return reflect.qual(self.__class__)
278
279     def getTypeToCopyFor(self, perspective):
280         """Determine what type tag to send for me.
281
282         By default, defer to self.L{getTypeToCopy}() normally this is
283         adequate, but you may override this to change it.
284         """
285
286         return self.getTypeToCopy()
287
288     def jellyFor(self, jellier):
289         """Assemble type tag and state to copy for this broker.
290
291         This will call L{getTypeToCopyFor} and L{getStateToCopy}, and
292         return an appropriate s-expression to represent me.
293         """
294
295         if jellier.invoker is None:
296             return getInstanceState(self, jellier)
297         p = jellier.invoker.serializingPerspective
298         t = self.getTypeToCopyFor(p)
299         state = self.getStateToCopyFor(p)
300         sxp = jellier.prepare(self)
301         sxp.extend([t, jellier.jelly(state)])
302         return jellier.preserve(self, sxp)
303
304
305 class Cacheable(Copyable):
306     """A cached instance.
307
308     This means that it's copied; but there is some logic to make sure
309     that it's only copied once.  Additionally, when state is retrieved,
310     it is passed a "proto-reference" to the state as it will exist on
311     the client.
312
313     XXX: The documentation for this class needs work, but it's the most
314     complex part of PB and it is inherently difficult to explain.
315     """
316
317     def getStateToCacheAndObserveFor(self, perspective, observer):
318         """
319         Get state to cache on the client and client-cache reference
320         to observe locally.
321
322         This is similiar to getStateToCopyFor, but it additionally
323         passes in a reference to the client-side RemoteCache instance
324         that will be created when it is unserialized.  This allows
325         Cacheable instances to keep their RemoteCaches up to date when
326         they change, such that no changes can occur between the point
327         at which the state is initially copied and the client receives
328         it that are not propogated.
329         """
330
331         return self.getStateToCopyFor(perspective)
332
333     def jellyFor(self, jellier):
334         """Return an appropriate tuple to serialize me.
335
336         Depending on whether this broker has cached me or not, this may
337         return either a full state or a reference to an existing cache.
338         """
339         if jellier.invoker is None:
340             return getInstanceState(self, jellier)
341         luid = jellier.invoker.cachedRemotelyAs(self, 1)
342         if luid is None:
343             luid = jellier.invoker.cacheRemotely(self)
344             p = jellier.invoker.serializingPerspective
345             type_ = self.getTypeToCopyFor(p)
346             observer = RemoteCacheObserver(jellier.invoker, self, p)
347             state = self.getStateToCacheAndObserveFor(p, observer)
348             l = jellier.prepare(self)
349             jstate = jellier.jelly(state)
350             l.extend([type_, luid, jstate])
351             return jellier.preserve(self, l)
352         else:
353             return cached_atom, luid
354
355     def stoppedObserving(self, perspective, observer):
356         """This method is called when a client has stopped observing me.
357
358         The 'observer' argument is the same as that passed in to
359         getStateToCacheAndObserveFor.
360         """
361
362
363
364 class RemoteCopy(Unjellyable):
365     """I am a remote copy of a Copyable object.
366
367     When the state from a L{Copyable} object is received, an instance will
368     be created based on the copy tags table (see setUnjellyableForClass) and
369     sent the L{setCopyableState} message.  I provide a reasonable default
370     implementation of that message; subclass me if you wish to serve as
371     a copier for remote data.
372
373     NOTE: copiers are invoked with no arguments.  Do not implement a
374     constructor which requires args in a subclass of L{RemoteCopy}!
375     """
376
377     def setCopyableState(self, state):
378         """I will be invoked with the state to copy locally.
379
380         'state' is the data returned from the remote object's
381         'getStateToCopyFor' method, which will often be the remote
382         object's dictionary (or a filtered approximation of it depending
383         on my peer's perspective).
384         """
385
386         self.__dict__ = state
387
388     def unjellyFor(self, unjellier, jellyList):
389         if unjellier.invoker is None:
390             return setInstanceState(self, unjellier, jellyList)
391         self.setCopyableState(unjellier.unjelly(jellyList[1]))
392         return self
393
394
395
396 class RemoteCache(RemoteCopy, Serializable):
397     """A cache is a local representation of a remote L{Cacheable} object.
398
399     This represents the last known state of this object.  It may
400     also have methods invoked on it -- in order to update caches,
401     the cached class generates a L{pb.RemoteReference} to this object as
402     it is originally sent.
403
404     Much like copy, I will be invoked with no arguments.  Do not
405     implement a constructor that requires arguments in one of my
406     subclasses.
407     """
408
409     def remoteMessageReceived(self, broker, message, args, kw):
410         """A remote message has been received.  Dispatch it appropriately.
411
412         The default implementation is to dispatch to a method called
413         'C{observe_messagename}' and call it on my  with the same arguments.
414         """
415
416         args = broker.unserialize(args)
417         kw = broker.unserialize(kw)
418         method = getattr(self, "observe_%s" % message)
419         try:
420             state = apply(method, args, kw)
421         except TypeError:
422             log.msg("%s didn't accept %s and %s" % (method, args, kw))
423             raise
424         return broker.serialize(state, None, method, args, kw)
425
426     def jellyFor(self, jellier):
427         """serialize me (only for the broker I'm for) as the original cached reference
428         """
429         if jellier.invoker is None:
430             return getInstanceState(self, jellier)
431         assert jellier.invoker is self.broker, "You cannot exchange cached proxies between brokers."
432         return 'lcache', self.luid
433
434
435     def unjellyFor(self, unjellier, jellyList):
436         if unjellier.invoker is None:
437             return setInstanceState(self, unjellier, jellyList)
438         self.broker = unjellier.invoker
439         self.luid = jellyList[1]
440         cProxy = _newDummyLike(self)
441         # XXX questionable whether this was a good design idea...
442         init = getattr(cProxy, "__init__", None)
443         if init:
444             init()
445         unjellier.invoker.cacheLocally(jellyList[1], self)
446         cProxy.setCopyableState(unjellier.unjelly(jellyList[2]))
447         # Might have changed due to setCopyableState method; we'll assume that
448         # it's bad form to do so afterwards.
449         self.__dict__ = cProxy.__dict__
450         # chomp, chomp -- some existing code uses "self.__dict__ =", some uses
451         # "__dict__.update".  This is here in order to handle both cases.
452         self.broker = unjellier.invoker
453         self.luid = jellyList[1]
454         return cProxy
455
456 ##     def __really_del__(self):
457 ##         """Final finalization call, made after all remote references have been lost.
458 ##         """
459
460     def __cmp__(self, other):
461         """Compare me [to another RemoteCache.
462         """
463         if isinstance(other, self.__class__):
464             return cmp(id(self.__dict__), id(other.__dict__))
465         else:
466             return cmp(id(self.__dict__), other)
467
468     def __hash__(self):
469         """Hash me.
470         """
471         return int(id(self.__dict__) % sys.maxint)
472
473     broker = None
474     luid = None
475
476     def __del__(self):
477         """Do distributed reference counting on finalize.
478         """
479         try:
480             # log.msg( ' --- decache: %s %s' % (self, self.luid) )
481             if self.broker:
482                 self.broker.decCacheRef(self.luid)
483         except:
484             log.deferr()
485
486 def unjellyCached(unjellier, unjellyList):
487     luid = unjellyList[1]
488     cNotProxy = unjellier.invoker.cachedLocallyAs(luid)
489     cProxy = _newDummyLike(cNotProxy)
490     return cProxy
491
492 setUnjellyableForClass("cached", unjellyCached)
493
494 def unjellyLCache(unjellier, unjellyList):
495     luid = unjellyList[1]
496     obj = unjellier.invoker.remotelyCachedForLUID(luid)
497     return obj
498
499 setUnjellyableForClass("lcache", unjellyLCache)
500
501 def unjellyLocal(unjellier, unjellyList):
502     obj = unjellier.invoker.localObjectForID(unjellyList[1])
503     return obj
504
505 setUnjellyableForClass("local", unjellyLocal)
506
507 class RemoteCacheMethod:
508     """A method on a reference to a L{RemoteCache}.
509     """
510
511     def __init__(self, name, broker, cached, perspective):
512         """(internal) initialize.
513         """
514         self.name = name
515         self.broker = broker
516         self.perspective = perspective
517         self.cached = cached
518
519     def __cmp__(self, other):
520         return cmp((self.name, self.broker, self.perspective, self.cached), other)
521
522     def __hash__(self):
523         return hash((self.name, self.broker, self.perspective, self.cached))
524
525     def __call__(self, *args, **kw):
526         """(internal) action method.
527         """
528         cacheID = self.broker.cachedRemotelyAs(self.cached)
529         if cacheID is None:
530             from pb import ProtocolError
531             raise ProtocolError("You can't call a cached method when the object hasn't been given to the peer yet.")
532         return self.broker._sendMessage('cache', self.perspective, cacheID, self.name, args, kw)
533
534 class RemoteCacheObserver:
535     """I am a reverse-reference to the peer's L{RemoteCache}.
536
537     I am generated automatically when a cache is serialized.  I
538     represent a reference to the client's L{RemoteCache} object that
539     will represent a particular L{Cacheable}; I am the additional
540     object passed to getStateToCacheAndObserveFor.
541     """
542
543     def __init__(self, broker, cached, perspective):
544         """(internal) Initialize me.
545
546         @param broker: a L{pb.Broker} instance.
547
548         @param cached: a L{Cacheable} instance that this L{RemoteCacheObserver}
549             corresponds to.
550
551         @param perspective: a reference to the perspective who is observing this.
552         """
553
554         self.broker = broker
555         self.cached = cached
556         self.perspective = perspective
557
558     def __repr__(self):
559         return "<RemoteCacheObserver(%s, %s, %s) at %s>" % (
560             self.broker, self.cached, self.perspective, id(self))
561
562     def __hash__(self):
563         """Generate a hash unique to all L{RemoteCacheObserver}s for this broker/perspective/cached triplet
564         """
565
566         return (  (hash(self.broker) % 2**10)
567                 + (hash(self.perspective) % 2**10)
568                 + (hash(self.cached) % 2**10))
569
570     def __cmp__(self, other):
571         """Compare me to another L{RemoteCacheObserver}.
572         """
573
574         return cmp((self.broker, self.perspective, self.cached), other)
575
576     def callRemote(self, _name, *args, **kw):
577         """(internal) action method.
578         """
579         cacheID = self.broker.cachedRemotelyAs(self.cached)
580         if cacheID is None:
581             from pb import ProtocolError
582             raise ProtocolError("You can't call a cached method when the "
583                                 "object hasn't been given to the peer yet.")
584         return self.broker._sendMessage('cache', self.perspective, cacheID,
585                                         _name, args, kw)
586
587     def remoteMethod(self, key):
588         """Get a L{pb.RemoteMethod} for this key.
589         """
590         return RemoteCacheMethod(key, self.broker, self.cached, self.perspective)