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 fullpath="PLC.Methods." + method
148 fullpath=self.other_methods_map[method]
149 module = __import__(fullpath, globals(), locals(), [classname])
150 return getattr(module, classname)(self)
151 except ImportError, AttributeError:
152 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
154 def call(self, source, method, *args):
156 Call the named method from the specified source with the
160 function = self.callable(method)
161 function.source = source
162 return function(*args)
164 def handle(self, source, data):
166 Handle an XML-RPC or SOAP request from the specified source.
169 # Parse request into method name and arguments
171 interface = xmlrpclib
172 (args, method) = xmlrpclib.loads(data)
173 methodresponse = True
175 if SOAPpy is not None:
177 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
180 # XXX Support named arguments
185 result = self.call(source, method, *args)
186 except PLCFault, fault:
187 # Handle expected faults
188 if interface == xmlrpclib:
190 methodresponse = None
191 elif interface == SOAPpy:
192 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
193 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
196 if interface == xmlrpclib:
197 if not isinstance(result, PLCFault):
199 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
200 elif interface == SOAPpy:
201 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)