2to3 -f has_key
[sfa.git] / sfa / server / xmlrpcapi.py
1 #
2 # SFA XML-RPC and SOAP interfaces
3 #
4
5 import string
6
7 # SOAP support is optional
8 try:
9     import SOAPpy
10     from SOAPpy.Parser import parseSOAPRPC
11     from SOAPpy.Types import faultType
12     from SOAPpy.NS import NS
13     from SOAPpy.SOAPBuilder import buildSOAP
14 except ImportError:
15     SOAPpy = None
16
17 ####################
18 #from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault
19 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
20 from sfa.util.sfalogging import logger
21 from sfa.util.py23 import xmlrpc_client
22
23 ####################
24 # See "2.2 Characters" in the XML specification:
25 #
26 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
27 # avoiding
28 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
29
30 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
31 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
32
33 def xmlrpclib_escape(s, replace = string.replace):
34     """
35     xmlrpclib does not handle invalid 7-bit control characters. This
36     function augments xmlrpclib.escape, which by default only replaces
37     '&', '<', and '>' with entities.
38     """
39
40     # This is the standard xmlrpclib.escape function
41     s = replace(s, "&", "&amp;")
42     s = replace(s, "<", "&lt;")
43     s = replace(s, ">", "&gt;",)
44
45     # Replace invalid 7-bit control characters with '?'
46     return s.translate(xml_escape_table)
47
48 def xmlrpclib_dump(self, value, write):
49     """
50     xmlrpclib cannot marshal instances of subclasses of built-in
51     types. This function overrides xmlrpclib.Marshaller.__dump so that
52     any value that is an instance of one of its acceptable types is
53     marshalled as that type.
54
55     xmlrpclib also cannot handle invalid 7-bit control characters. See
56     above.
57     """
58
59     # Use our escape function
60     args = [self, value, write]
61     if isinstance(value, (str, unicode)):
62         args.append(xmlrpclib_escape)
63
64     try:
65         # Try for an exact match first
66         f = self.dispatch[type(value)]
67     except KeyError:
68         raise
69         # Try for an isinstance() match
70         for Type, f in self.dispatch.iteritems():
71             if isinstance(value, Type):
72                 f(*args)
73                 return
74         raise TypeError("cannot marshal %s objects" % type(value))
75     else:
76         f(*args)
77
78 # You can't hide from me!
79 # Note: not quite  sure if this will still cause
80 # the expected behaviour under python3
81 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
82
83 class XmlrpcApi:
84     """
85     The XmlrpcApi class implements a basic xmlrpc (or soap) service 
86     """
87
88     protocol = None
89   
90     def __init__ (self, encoding="utf-8", methods='sfa.methods'):
91
92         self.encoding = encoding
93         self.source = None 
94         
95         # flat list of method names
96         self.methods_module = methods_module = __import__(methods, fromlist=[methods])
97         self.methods = methods_module.all
98
99         self.logger = logger
100  
101     def callable(self, method):
102         """
103         Return a new instance of the specified method.
104         """
105         # Look up method
106         if method not in self.methods:
107             raise SfaInvalidAPIMethod(method)
108         
109         # Get new instance of method
110         try:
111             classname = method.split(".")[-1]
112             module = __import__(self.methods_module.__name__ + "." + method, globals(), locals(), [classname])
113             callablemethod = getattr(module, classname)(self)
114             return getattr(module, classname)(self)
115         except (ImportError, AttributeError):
116             self.logger.log_exc("Error importing method: %s" % method)
117             raise SfaInvalidAPIMethod(method)
118
119     def call(self, source, method, *args):
120         """
121         Call the named method from the specified source with the
122         specified arguments.
123         """
124         function = self.callable(method)
125         function.source = source
126         self.source = source
127         return function(*args)
128
129     
130     def handle(self, source, data, method_map):
131         """
132         Handle an XML-RPC or SOAP request from the specified source.
133         """
134         # Parse request into method name and arguments
135         try:
136             interface = xmlrpc_client
137             self.protocol = 'xmlrpc'
138             (args, method) = xmlrpc_client.loads(data)
139             if method in method_map:
140                 method = method_map[method]
141             methodresponse = True
142             
143         except Exception as e:
144             if SOAPpy is not None:
145                 self.protocol = 'soap'
146                 interface = SOAPpy
147                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
148                 method = r._name
149                 args = r._aslist()
150                 # XXX Support named arguments
151             else:
152                 raise e
153
154         try:
155             result = self.call(source, method, *args)
156         except SfaFault as fault:
157             result = fault
158             self.logger.log_exc("XmlrpcApi.handle has caught Exception") 
159         except Exception as fault:
160             self.logger.log_exc("XmlrpcApi.handle has caught Exception")
161             result = SfaAPIError(fault)
162
163
164         # Return result
165         response = self.prepare_response(result, method)
166         return response
167     
168     def prepare_response(self, result, method=""):
169         """
170         convert result to a valid xmlrpc or soap response
171         """   
172  
173         if self.protocol == 'xmlrpc':
174             if not isinstance(result, SfaFault):
175                 result = (result,)
176             response = xmlrpc_client.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
177         elif self.protocol == 'soap':
178             if isinstance(result, Exception):
179                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
180                 result._setDetail("Fault %d: %s" % (result.faultCode, result.faultString))
181             else:
182                 response = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
183         else:
184             if isinstance(result, Exception):
185                 raise result 
186             
187         return response
188