2 # PLCAPI XML-RPC and SOAP interfaces
4 # Aaron Klingaman <alk@absarokasoft.com>
5 # Mark Huang <mlhuang@cs.princeton.edu>
7 # Copyright (C) 2004-2006 The Trustees of Princeton University
17 # See "2.2 Characters" in the XML specification:
19 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
21 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
23 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
24 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
26 def xmlrpclib_escape(s, replace = string.replace):
28 xmlrpclib does not handle invalid 7-bit control characters. This
29 function augments xmlrpclib.escape, which by default only replaces
30 '&', '<', and '>' with entities.
33 # This is the standard xmlrpclib.escape function
34 s = replace(s, "&", "&")
35 s = replace(s, "<", "<")
36 s = replace(s, ">", ">",)
38 # Replace invalid 7-bit control characters with '?'
39 return s.translate(xml_escape_table)
41 def xmlrpclib_dump(self, value, write):
43 xmlrpclib cannot marshal instances of subclasses of built-in
44 types. This function overrides xmlrpclib.Marshaller.__dump so that
45 any value that is an instance of one of its acceptable types is
46 marshalled as that type.
48 xmlrpclib also cannot handle invalid 7-bit control characters. See
52 # Use our escape function
53 args = [self, value, write]
54 if isinstance(value, (str, unicode)):
55 args.append(xmlrpclib_escape)
58 # Try for an exact match first
59 f = self.dispatch[type(value)]
61 # Try for an isinstance() match
62 for Type, f in self.dispatch.iteritems():
63 if isinstance(value, Type):
66 raise TypeError, "cannot marshal %s objects" % type(value)
70 # You can't hide from me!
71 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
73 # SOAP support is optional
76 from SOAPpy.Parser import parseSOAPRPC
77 from SOAPpy.Types import faultType
78 from SOAPpy.NS import NS
79 from SOAPpy.SOAPBuilder import buildSOAP
83 from PLC.Config import Config
84 from PLC.Faults import *
89 def import_deep(name):
90 mod = __import__(name)
91 components = name.split('.')
92 for comp in components[1:]:
93 mod = getattr(mod, comp)
98 # flat list of method names
99 native_methods = PLC.Methods.native_methods
101 # other_methods_map : dict {methodname: fullpath}
102 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
104 for subdir in [ 'Accessors', 'Legacy']:
106 # scan e.g. PLC.Accessors.__all__
107 pkg = __import__(path).__dict__[subdir]
108 for modulename in getattr(pkg,"__all__"):
109 fullpath=path+"."+modulename
110 for method in getattr(import_deep(fullpath),"methods"):
111 other_methods_map[method] = fullpath
113 all_methods = native_methods + other_methods_map.keys()
115 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
116 self.encoding = encoding
118 # Better just be documenting the API
123 self.config = Config(config)
125 # Initialize database connection
126 if self.config.PLC_DB_TYPE == "postgresql":
127 from PLC.PostgreSQL import PostgreSQL
128 self.db = PostgreSQL(self)
131 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
133 def callable(self, method):
135 Return a new instance of the specified method.
139 if method not in self.all_methods:
140 raise PLCInvalidAPIMethod, method
142 # Get new instance of method
144 classname = method.split(".")[-1]
145 if method in self.native_methods:
146 module = __import__("PLC.Methods." + method, globals(), locals(), [classname])
147 return getattr(module, classname)(self)
149 fullpath=self.other_methods_map[method]
150 module = __import__(fullpath, globals(), locals(), [classname])
151 return getattr(module, classname)(self)
152 except ImportError, AttributeError:
153 raise PLCInvalidAPIMethod, method
155 def call(self, source, method, *args):
157 Call the named method from the specified source with the
161 function = self.callable(method)
162 function.source = source
163 return function(*args)
165 def handle(self, source, data):
167 Handle an XML-RPC or SOAP request from the specified source.
170 # Parse request into method name and arguments
172 interface = xmlrpclib
173 (args, method) = xmlrpclib.loads(data)
174 methodresponse = True
176 if SOAPpy is not None:
178 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
181 # XXX Support named arguments
186 result = self.call(source, method, *args)
187 except PLCFault, fault:
188 # Handle expected faults
189 if interface == xmlrpclib:
191 methodresponse = None
192 elif interface == SOAPpy:
193 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
194 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
197 if interface == xmlrpclib:
198 if not isinstance(result, PLCFault):
200 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
201 elif interface == SOAPpy:
202 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)