commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

gnue/common doc/RPC-abstraction.txt src/GComm.p...


From: Jason Cater
Subject: gnue/common doc/RPC-abstraction.txt src/GComm.p...
Date: Sun, 09 Dec 2001 23:41:02 -0500

CVSROOT:        /home/cvs
Module name:    gnue
Changes by:     Jason Cater <address@hidden>    01/12/09 23:41:02

Modified files:
        common/doc     : RPC-abstraction.txt 
        common/src     : GComm.py 
        common/src/commdrivers/_helpers: AsyncSocketServer.py 
        common/src/commdrivers/_test: server.py test.py 
        common/src/commdrivers/proxy: CommDriver.py 
        common/src/commdrivers/xmlrpc: CommDriver.py 
        common/src/dbdrivers/pypgsql: DBdriver.py 

Log message:
        one step closer to a working gnurpc

CVSWeb URLs:
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/doc/RPC-abstraction.txt.diff?cvsroot=OldCVS&tr1=1.3&tr2=1.4&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/GComm.py.diff?cvsroot=OldCVS&tr1=1.11&tr2=1.12&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py.diff?cvsroot=OldCVS&tr1=1.3&tr2=1.4&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/commdrivers/_test/server.py.diff?cvsroot=OldCVS&tr1=1.4&tr2=1.5&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/commdrivers/_test/test.py.diff?cvsroot=OldCVS&tr1=1.1&tr2=1.2&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/commdrivers/proxy/CommDriver.py.diff?cvsroot=OldCVS&tr1=1.1&tr2=1.2&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/commdrivers/xmlrpc/CommDriver.py.diff?cvsroot=OldCVS&tr1=1.10&tr2=1.11&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/gnue/common/src/dbdrivers/pypgsql/DBdriver.py.diff?cvsroot=OldCVS&tr1=1.14&tr2=1.15&r1=text&r2=text

Patches:
Index: gnue/common/doc/RPC-abstraction.txt
diff -c gnue/common/doc/RPC-abstraction.txt:1.3 
gnue/common/doc/RPC-abstraction.txt:1.4
*** gnue/common/doc/RPC-abstraction.txt:1.3     Thu Dec  6 01:26:35 2001
--- gnue/common/doc/RPC-abstraction.txt Sun Dec  9 23:41:02 2001
***************
*** 1,9 ****
  Objective
  =========
! Provide an abstract model for providing services to the world and 
  for requesting services from a compatable provider.
  
  
  Motivation
  ==========
  
--- 1,10 ----
  Objective
  =========
! Provide an abstract model for providing services to the world and
  for requesting services from a compatable provider.
  
  
+ 
  Motivation
  ==========
  
***************
*** 34,40 ****
  GComm tools will support the new encryption model.
  
  
!  Provider/Server
                    .-------------.                  .-------------.
                    |             | --> XML-RPC  <-- |             |
  MyFunction1() --> |    GComm    | -->  SOAP    <-- |    GComm    |
--- 35,41 ----
  GComm tools will support the new encryption model.
  
  
!  Provider/Server                                              Requester/Client
                    .-------------.                  .-------------.
                    |             | --> XML-RPC  <-- |             |
  MyFunction1() --> |    GComm    | -->  SOAP    <-- |    GComm    |
***************
*** 43,60 ****
                    |             | -->  Other   <-- |             |
                    `-------------'                  `-------------'
  
! Pitfalls
! ========
  
! This design only exposes services. It does not try to emulate Object
! management for transports that do not support Object management natively.
! Such a design is far too complicated of a task for GNUe to embark upon
! (and would be a futile exercise as there are plenty of existing Object
! management tools.) If objects are used in a non-object transport, a
! simple reference passing mechanism will be used and all objects will be
! maintained/accessed by the server.
  
  
  Design Considerations
  =====================
  
--- 44,69 ----
                    |             | -->  Other   <-- |             |
                    `-------------'                  `-------------'
  
! 
  
! Remote Objects vs Remote Services
! =================================
  
+ This design is meant to exposes services. It does not try to emulate
+ Object management for transports that do not support Object management
+ natively. Such a design is far too complicated of a task for GNUe to
+ embark upon (and would be a futile exercise as there are plenty of
+ existing Object management tools.) If objects are used in a non-object
+ transport, a simple reference passing mechanism will be used and all
+ objects will be maintained/accessed by the server.
+ 
+ However, if your design requires the use of remote objects, you might
+ need to re-evaluate your design as, all-to-often, objects are overkill
+ for remote services. (I'm sure we will get flamed for that statement,
+ but it's true.)
  
+ 
+ 
  Design Considerations
  =====================
  
***************
*** 99,104 ****
--- 108,114 ----
         Mac specific if necessary.)
  
  
+ 
  Features
  ========
  
***************
*** 108,114 ****
     whatever error mechanism that adapter provides.  Should the
     adapter provide named exceptions, then
  
!  * Short Circuit/Proxy Mode
     GComm provides a "short circuit" mode so that two modules designed to
     run as separate servers can be run under the same server instance and
     access each others services without using an external protocol. In
--- 118,124 ----
     whatever error mechanism that adapter provides.  Should the
     adapter provide named exceptions, then
  
!  * Loopback/Local Proxy Mode
     GComm provides a "short circuit" mode so that two modules designed to
     run as separate servers can be run under the same server instance and
     access each others services without using an external protocol. In
***************
*** 120,157 ****
  
  
  
!                                                                  OpenOffice
!                       Corba     Pyro   Java RMI  XML-RPC   SOAP     UNO
!                      -------- -------- -------- -------- -------- --------
! Distributed Objects      X        X        X        -        ??       X
  
! Exceptions               X        X        X        X        ??       X
  
! Pass simple types        X        X        X        X        X        X
! Pass aggegrate types     X        X        X        X        ??       X
! Pass userdef types       X        X        X        ??       ??       X
  
- Return simple types      X        X        X        ??       ??       X
- Return aggregate types   X        X        X        ??       ??       X
- Return userdef types     X        X        X        ??       ??       X
  
- Python Native            -        X       ??        X        X        -
- Python Bindings          X        X       ??        X        X        -
  
  
  
! Typical sequence of events
! ==========================
  
! Client program needs to use services on Server X
! As specified by the end-user in its configuration file,
! it will use the XML-RPC adapter.
  
! The client program gets a GComm interface instance
! by calling GComm.attach()
  
  
  
  Simple Example
  ==============
  
--- 130,175 ----
  
  
  
! How a client works
! ==================
  
! Client program needs to use services on Server X.
! As specified by the end-user in its configuration file,
! it will use the XML-RPC adapter.
  
! The client program gets a GComm interface instance
! by calling GComm.attach(). After attaching,
  
  
  
+ How the server works
+ ====================
  
+ First, some terminology:
  
!   * Interface: In Serverland, an Interface one of the methods of exporting
!     our services.  CORBA, XML-RPC, SOAP, and SOCKETS are four separate
!     Interfaces. A commdriver is written for each Interface.
  
!   * ServerAdapter: Part of the commdriver, the ServerAdapter implements
!     the required server-side code to run an Interface.
  
! 
! An application makes a call to GComm.bind() with a list of requested
! interfaces and the location of its 
! 
! A ServerAdapter is loaded from commdrivers for each requested interface.
! Currently, each ServerAdapter is forked into a separate process and
! loops indefinitely. This is to ease the implementation of concurrently
! running interfaces, so that the loops for CORBA will not interfere with
! the loops for XML-RPC or SOCKETS (and so on...)  This may change in the
! future (i.e., to a separate thread for each interface or to a select()
! based polling mechanism)
  
  
  
+ 
+ 
  Simple Example
  ==============
  
***************
*** 186,192 ****
  
  
  
! Example Python/GNUe-Common Client:
  
  
    params = { 'host': 'myserver.mydomain',
--- 204,211 ----
  
  
  
! Example Client
! ==============
  
  
    params = { 'host': 'myserver.mydomain',
***************
*** 196,202 ****
    server = GComm.attach('xmlrpc', params)
  
  
!   print "Donut Plant Operational Status: ", 
    print server.DonutProvider.Management.Status()
  
  
--- 215,221 ----
    server = GComm.attach('xmlrpc', params)
  
  
!   print "Donut Plant Operational Status: ",
    print server.DonutProvider.Management.Status()
  
  
***************
*** 211,214 ****
--- 230,253 ----
    server.close()
  
  
+ 
+ Possible Interfaces
+ ===================
+                                                                  OpenOffice
+                       Corba     Pyro   Java RMI  XML-RPC   SOAP     UNO
+                      -------- -------- -------- -------- -------- --------
+ Distributed Objects      X        X        X        -        ??       X
+ 
+ Exceptions               X        X        X        X        ??       X
+ 
+ Pass simple types        X        X        X        X        X        X
+ Pass aggegrate types     X        X        X        X        ??       X
+ Pass userdef types       X        X        X        ??       ??       X
+ 
+ Return simple types      X        X        X        ??       ??       X
+ Return aggregate types   X        X        X        ??       ??       X
+ Return userdef types     X        X        X        ??       ??       X
+ 
+ Python Native            -        X       ??        X        X        -
+ Python Bindings          X        X       ??        X        X        -
  
Index: gnue/common/src/GComm.py
diff -c gnue/common/src/GComm.py:1.11 gnue/common/src/GComm.py:1.12
*** gnue/common/src/GComm.py:1.11       Thu Dec  6 18:19:41 2001
--- gnue/common/src/GComm.py    Sun Dec  9 23:41:02 2001
***************
*** 59,65 ****
  #
  # Attributes:
  #
! # rpcdef     The RPC definition loaded from an XML file
  #
  # drivers    A dictionary that defines all the transports to be used. The
  #            dictionary is of the form: {driver:params} where:
--- 59,70 ----
  #
  # Attributes:
  #
! # rpcdef     The RPC definition loaded from an XML file. This can be
! #              1) a string that contains the location of the XML file
! #                 (can be a URL or pathname)
! #              2) a file-handle (or file-like handle; e.g. StringBuffer)
! #                 referencing an XML definition
! #              3) a GnuRpc object created from GComm.loadDefinition()
  #
  # drivers    A dictionary that defines all the transports to be used. The
  #            dictionary is of the form: {driver:params} where:
***************
*** 74,79 ****
--- 79,102 ----
  #              2) handler: Method that when called returns a class instance
  #
  def bind(rpcdef, drivers, bindings):
+ 
+   if type(rpcdef) == type(""):
+ 
+     # Application supplied the location of their rpcdef.. load it
+     fin = openResource(rpcdef)
+     mapping = loadDefinition(fin,1)
+     fin.close()
+ 
+   elif rpcdef.hasattr("read"):
+ 
+     # Application provided us a file-like object
+     mapping = loadDefinition(rpcdef)
+ 
+   else:
+ 
+     # Otherwise, they must have specified a GnuRpc object
+     mapping = rpcdef
+ 
  
    servers = {}
  
Index: gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py
diff -c gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py:1.3 
gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py:1.4
*** gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py:1.3       Thu Dec 
 6 18:19:41 2001
--- gnue/common/src/commdrivers/_helpers/AsyncSocketServer.py   Sun Dec  9 
23:41:02 2001
***************
*** 22,28 ****
  # AsyncSocketServer
  #
  # DESCRIPTION:
! # Set of classes that implement a Asynchronios IO-based socket server.
  #
  # NOTES:
  # These socket servers do their magic without threads or forking.
--- 22,28 ----
  # AsyncSocketServer
  #
  # DESCRIPTION:
! # Set of classes that implement a Async-IO-based socket server.
  #
  # NOTES:
  # These socket servers do their magic without threads or forking.
Index: gnue/common/src/commdrivers/_test/server.py
diff -c gnue/common/src/commdrivers/_test/server.py:1.4 
gnue/common/src/commdrivers/_test/server.py:1.5
*** gnue/common/src/commdrivers/_test/server.py:1.4     Thu Dec  6 18:19:42 2001
--- gnue/common/src/commdrivers/_test/server.py Sun Dec  9 23:41:02 2001
***************
*** 3,12 ****
  ################################################
  # Define our Donut stuff
  ################################################
  
  class DonutPlace:
    def __init__(self):
!     self.Bakery = BakingUnit()
      self.Management = Management()
  
  
--- 3,16 ----
  ################################################
  # Define our Donut stuff
  ################################################
+ #
+ # Our donut classes aren't documented. They
+ # are not the interesting part of this file :)
+ #
  
  class DonutPlace:
    def __init__(self):
!     self.Bakery = Bakery()
      self.Management = Management()
  
  
***************
*** 23,28 ****
--- 27,37 ----
      return "A-Ok!"
  
  
+ class Bakery:
+   def getBakingUnit(self, flavor):
+     return BakingUnit(flavor, "Memphis, TN Bakery #1")
+ 
+ 
  class BakingUnit:
    flavor = "glazed"
    unitLocation = "Memphis, TN Bakery #1"
***************
*** 45,54 ****
  
  
  
- class Bakery:
-   def getBakingUnit(self, flavor):
-     return BakingUnit(flavor, "Memphis, TN Bakery #1")
- 
  
  
  ################################################
--- 54,59 ----
***************
*** 58,90 ****
  from gnue.common import GComm, openResource
  from gnue.common.GServerApp import GServerApp
  
  class DonutServery(GServerApp):
  
    def run(self):
- 
-     fin = openResource("donuts.grpc")
-     rpcdef = GComm.loadDefinition(fin,1)
-     fin.close()
- 
  
!     # Expose our services to the world
!     params = { 'port': 8765 }
!     GComm.bind ( rpcdef,
!                 {'xmlrpc': params },
!                 {'DonutPlace': self.requestDonutPlace })
  
      # Daemonize (if appropriate)
      GServerApp.run(self)
  
    def requestDonutPlace(self):
      # In real applications, this would probably either
      #  1) Maintain a single DonutPlace that was always used, or
      #  2) Maintain a pool of DonutPlaces that got recycled.
      return DonutPlace()
  
- 
  
  if __name__ == '__main__':
    DonutServery().run()
  
  
--- 63,171 ----
  from gnue.common import GComm, openResource
  from gnue.common.GServerApp import GServerApp
  
+ 
+ #
+ # Create a Server instance and expose our services.
+ #
+ # GServerApp handles all the nasty server/daemon stuff
+ # (like going into the background, etc)
+ #
  class DonutServery(GServerApp):
  
    def run(self):
  
!     # Create the various servers
!     bind()
  
      # Daemonize (if appropriate)
      GServerApp.run(self)
  
+ 
    def requestDonutPlace(self):
      # In real applications, this would probably either
      #  1) Maintain a single DonutPlace that was always used, or
      #  2) Maintain a pool of DonutPlaces that got recycled.
+     # We are just testing transports, so we can cheat :)
      return DonutPlace()
  
  
+ #
+ # The transports to use
+ #
+ # This hash gets set via the various
+ # bind_* methods (below)
+ #
+ transports = {}
+ 
+ 
+ #
+ # Bind to the various server transports
+ #
+ # Normally, this kind of code would be in
+ # the DonutServery.run() method. It was
+ # taken out of the DonutServery class so
+ # it can be used by the proxy tests.
+ #
+ def bind()
+ 
+ 
+   # Expose our services to the world
+   return GComm.bind ( "donuts.grpc",
+                       transports,
+                       {'DonutPlace': self.requestDonutPlace })
+ 
+ 
+ 
+ #
+ #
+ # Test harnesses
+ #
+ #
+ 
+ #
+ # Use the xmlrpc interface
+ #
+ # This binds to port 8765 and exposes an
+ # XML-RPC interface (using http or https)
+ #
+ def bind_xmlrpc():
+   print "Exporting our services via xmlrpc..."
+   transports['xmlrpc'] =  { 'port': 8765 }
+ 
+ 
+ #
+ # Use the sockets interface
+ #
+ # This binds to port 8766 and exposes our
+ # non-standard sockets interface.
+ #
+ def bind_sockets():
+   print "Exporting our services via sockets..."
+   transports['sockets'] =  { 'port': 8765 }
+ 
+ 
+ 
+ #
+ # Use the proxy transport
+ #
+ # This test is different than the others.
+ # It only gets called by the test.py script.
+ # It does not use GServerApp (since it doesn't
+ # actually go into a "server" mode.)
+ #
+ def create_proxy():
+   print "Exporting our services via proxy..."
+   return bind()
+ 
+ 
+ #
+ # main
+ #
  if __name__ == '__main__':
+ 
+   bind_xmlrpc()
+   #bind_sockets()
+ 
    DonutServery().run()
  
  
Index: gnue/common/src/commdrivers/_test/test.py
diff -c gnue/common/src/commdrivers/_test/test.py:1.1 
gnue/common/src/commdrivers/_test/test.py:1.2
*** gnue/common/src/commdrivers/_test/test.py:1.1       Wed Dec  5 18:15:58 2001
--- gnue/common/src/commdrivers/_test/test.py   Sun Dec  9 23:41:02 2001
***************
*** 1,20 ****
- params = { 'host': 'myserver.mydomain',
-            'port': 8765,
-            'transport': 'https' }
  
- server = GComm.attach('xmlrpc', params)
  
  
! print "Donut Plant Operational Status:"
! print server.DonutProvider.Management.Status()
  
  
! address = 'Jason Cater\n123 Main St\nMemphis, TN 38001"
! glazedBakery = server.DonutProvider.Factory.getDonutBakery('glazed yeast')
  
- print "Sending %s donuts to Jason Cater" % glazedBakery.get_flavor()
- print "Kitchen in use:", glazedBakery.get_unitLocation()
- print "Success: ", glazedBakery.requestDelivery(address)
  
  
! server.close()
--- 1,82 ----
  
  
+ #
+ # Primary testing routine
+ #
+ # This method demonstrates that the developer's code
+ # should not be dependent on the transport agent.
+ #
+ def test(interface, params):
  
!   print "Testing %s interface..." % interface
  
+   server = GComm.attach(interface, params)
  
!   print "  Donut Plant Operational Status:"
!   print server.DonutProvider.Management.Status()
  
  
+   address = 'Jason Cater\n123 Main St\nMemphis, TN 38001"
+   glazedBakery = server.DonutProvider.Bakery.getBakeryUnit('glazed yeast')
  
!   print "  Sending %s donuts to Jason Cater" % glazedBakery.get_flavor()
!   print "  Kitchen in use:", glazedBakery.get_unitLocation()
!   print "  Success: ", glazedBakery.requestDelivery(address)
!   print
! 
!   server.close()
! 
! 
! 
! #
! # Test the proxy interface
! #
! # This method actually starts a "server" instance in the
! # local namespace. The "server" doesn't actually connect
! # via any ports or such...
! #
! def test_proxy():
! 
!   import server
!   params = { '_proxy': server.create_proxy() }
!   test('proxy',params)
! 
! 
! #
! # Test the xmlrpc interface
! #
! # Expects server.py to be running via xmlrpc on this
! # machine's port 8765.
! #
! def test_xmlrpc():
! 
!   params = { 'host': 'localhost',
!              'port': 8765,
!              'transport': 'https' }
! 
!   test('xmlrpc',params)
! 
! 
! #
! # Test the sockets interface
! #
! # Expects server.py to be running via sockets on this
! # machine's port 8766.
! #
! def test_sockets():
! 
!   params = { 'host': 'localhost',
!              'port': 8766 }
! 
!   test('sockets',params)
! 
! 
! #
! # main
! #
! if __name__ == '__main__':
! 
!   test_proxy()
!   #test_xmlrpc()
!   #test_sockets()
! `
\ No newline at end of file
Index: gnue/common/src/commdrivers/proxy/CommDriver.py
diff -c gnue/common/src/commdrivers/proxy/CommDriver.py:1.1 
gnue/common/src/commdrivers/proxy/CommDriver.py:1.2
*** gnue/common/src/commdrivers/proxy/CommDriver.py:1.1 Tue Oct 30 01:13:21 2001
--- gnue/common/src/commdrivers/proxy/CommDriver.py     Sun Dec  9 23:41:02 2001
***************
*** 28,33 ****
--- 28,38 ----
  #
  # NOTES:
  #
+ # Client Parameters:
+ #
+ #   _proxy    The actual ServerAdapter instance. This has to be set by
+ #             the application, not by a config file.
+ #
  
  #
  # We provide both a client and a server driver...
Index: gnue/common/src/commdrivers/xmlrpc/CommDriver.py
diff -c gnue/common/src/commdrivers/xmlrpc/CommDriver.py:1.10 
gnue/common/src/commdrivers/xmlrpc/CommDriver.py:1.11
*** gnue/common/src/commdrivers/xmlrpc/CommDriver.py:1.10       Thu Dec  6 
18:19:42 2001
--- gnue/common/src/commdrivers/xmlrpc/CommDriver.py    Sun Dec  9 23:41:02 2001
***************
*** 22,31 ****
  # xmlrpc/CommDriver.py
  #
  # DESCRIPTION:
! # Class that implements the XML-RPC driver for GNUe Comm.
  #
  # NOTES:
  # Requires xmlrpclib from http://www.pythonware.com/products/xmlrpc/
  #
  # Client Parameters:
  #
--- 22,32 ----
  # xmlrpc/CommDriver.py
  #
  # DESCRIPTION:
! # Set of classes that implement the XML-RPC driver for GNUe Comm.
  #
  # NOTES:
  # Requires xmlrpclib from http://www.pythonware.com/products/xmlrpc/
+ # or Python 2.2
  #
  # Client Parameters:
  #
***************
*** 116,139 ****
      pass
  
  
-   def getAttribute(self, attribute):
-     pass
- 
  
-   def setAttribute(self, attribute):
-     pass
  
-     
- 
  ##############################################################################
  #
  # ServerAdapter
  #
! class ServerAdapter(AsyncHTTPServer):
  
    def __init__(self, params):
!     pass
  
    def raiseException(self, exception, message, event=None):
      xmlrpclib.dumps(xmlrpclib.Fault(34543, '%s: %s' % (exception, message)))
  
--- 117,134 ----
      pass
  
  
  
  
  ##############################################################################
  #
  # ServerAdapter
  #
! class ServerAdapter(HTTPServer):
  
    def __init__(self, params):
!     HTTPServer.__init__(self, params)
  
+     
    def raiseException(self, exception, message, event=None):
      xmlrpclib.dumps(xmlrpclib.Fault(34543, '%s: %s' % (exception, message)))
  
Index: gnue/common/src/dbdrivers/pypgsql/DBdriver.py
diff -c gnue/common/src/dbdrivers/pypgsql/DBdriver.py:1.14 
gnue/common/src/dbdrivers/pypgsql/DBdriver.py:1.15
*** gnue/common/src/dbdrivers/pypgsql/DBdriver.py:1.14  Tue Nov 20 21:16:52 2001
--- gnue/common/src/dbdrivers/pypgsql/DBdriver.py       Sun Dec  9 23:41:02 2001
***************
*** 218,232 ****
  
  
  class PG_DataObject_Object(PG_DataObject, \
!       DBSIG_DataObject_Object): 
  
!   def __init__(self): 
!     # Call DBSIG init first because PG_DataObject needs to overwrite 
      # some of its values
!     DBSIG_DataObject_Object.__init__(self) 
      PG_DataObject.__init__(self)
  
!   def _buildQuery(self, conditions={}): 
      return DBSIG_DataObject_Object._buildQuery(self, conditions)
  
  
--- 218,232 ----
  
  
  class PG_DataObject_Object(PG_DataObject, \
!       DBSIG_DataObject_Object):
  
!   def __init__(self):
!     # Call DBSIG init first because PG_DataObject needs to overwrite
      # some of its values
!     DBSIG_DataObject_Object.__init__(self)
      PG_DataObject.__init__(self)
  
!   def _buildQuery(self, conditions={}):
      return DBSIG_DataObject_Object._buildQuery(self, conditions)
  
  



reply via email to

[Prev in Thread] Current Thread [Next in Thread]