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
18 # See "2.2 Characters" in the XML specification:
20 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
22 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
24 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
25 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
27 def xmlrpclib_escape(s, replace = string.replace):
29 xmlrpclib does not handle invalid 7-bit control characters. This
30 function augments xmlrpclib.escape, which by default only replaces
31 '&', '<', and '>' with entities.
34 # This is the standard xmlrpclib.escape function
35 s = replace(s, "&", "&")
36 s = replace(s, "<", "<")
37 s = replace(s, ">", ">",)
39 # Replace invalid 7-bit control characters with '?'
40 return s.translate(xml_escape_table)
42 def xmlrpclib_dump(self, value, write):
44 xmlrpclib cannot marshal instances of subclasses of built-in
45 types. This function overrides xmlrpclib.Marshaller.__dump so that
46 any value that is an instance of one of its acceptable types is
47 marshalled as that type.
49 xmlrpclib also cannot handle invalid 7-bit control characters. See
53 # Use our escape function
54 args = [self, value, write]
55 if isinstance(value, (str, unicode)):
56 args.append(xmlrpclib_escape)
59 # Try for an exact match first
60 f = self.dispatch[type(value)]
62 # Try for an isinstance() match
63 for Type, f in self.dispatch.iteritems():
64 if isinstance(value, Type):
67 raise TypeError, "cannot marshal %s objects" % type(value)
71 # You can't hide from me!
72 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
74 # SOAP support is optional
77 from SOAPpy.Parser import parseSOAPRPC
78 from SOAPpy.Types import faultType
79 from SOAPpy.NS import NS
80 from SOAPpy.SOAPBuilder import buildSOAP
84 from PLC.Config import Config
85 from PLC.Faults import *
90 def import_deep(name):
91 mod = __import__(name)
92 components = name.split('.')
93 for comp in components[1:]:
94 mod = getattr(mod, comp)
99 # flat list of method names
100 native_methods = PLC.Methods.native_methods
101 legacy_methods = PLC.Legacy.native_methods
103 # other_methods_map : dict {methodname: fullpath}
104 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
106 for subdir in [ 'Accessors' ]:
108 # scan e.g. PLC.Accessors.__all__
109 pkg = __import__(path).__dict__[subdir]
110 for modulename in getattr(pkg,"__all__"):
111 fullpath=path+"."+modulename
112 for method in getattr(import_deep(fullpath),"methods"):
113 other_methods_map[method] = fullpath
115 all_methods = native_methods + legacy_methods + other_methods_map.keys()
117 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
118 self.encoding = encoding
120 # Better just be documenting the API
125 self.config = Config(config)
127 # Initialize database connection
128 if self.config.PLC_DB_TYPE == "postgresql":
129 from PLC.PostgreSQL import PostgreSQL
130 self.db = PostgreSQL(self)
133 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
135 def callable(self, method):
137 Return a new instance of the specified method.
141 if method not in self.all_methods:
142 raise PLCInvalidAPIMethod, method
144 # Get new instance of method
146 classname = method.split(".")[-1]
147 if method in self.native_methods:
148 fullpath="PLC.Methods." + method
149 elif method in self.legacy_methods:
150 fullpath="PLC.Legacy." + method
152 fullpath=self.other_methods_map[method]
153 module = __import__(fullpath, globals(), locals(), [classname])
154 return getattr(module, classname)(self)
155 except ImportError, AttributeError:
156 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
158 def call(self, source, method, *args):
160 Call the named method from the specified source with the
164 function = self.callable(method)
165 function.source = source
166 return function(*args)
168 def handle(self, source, data):
170 Handle an XML-RPC or SOAP request from the specified source.
173 # Parse request into method name and arguments
175 interface = xmlrpclib
176 (args, method) = xmlrpclib.loads(data)
177 methodresponse = True
179 if SOAPpy is not None:
181 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
184 # XXX Support named arguments
189 result = self.call(source, method, *args)
190 except PLCFault, fault:
191 # Handle expected faults
192 if interface == xmlrpclib:
194 methodresponse = None
195 elif interface == SOAPpy:
196 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
197 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
200 if interface == xmlrpclib:
201 if not isinstance(result, PLCFault):
203 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
204 elif interface == SOAPpy:
205 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)