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 *
88 def import_deep(name):
89 mod = __import__(name)
90 components = name.split('.')
91 for comp in components[1:]:
92 mod = getattr(mod, comp)
97 # flat list of method names
98 native_methods = PLC.Methods.native_methods
100 # other_methods_map : dict {methodname: fullpath}
101 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
103 for subdir in [ 'Accessors' ]:
105 # scan e.g. PLC.Accessors.__all__
106 pkg = __import__(path).__dict__[subdir]
107 for modulename in getattr(pkg,"__all__"):
108 fullpath=path+"."+modulename
109 for method in getattr(import_deep(fullpath),"methods"):
110 other_methods_map[method] = fullpath
112 all_methods = native_methods + other_methods_map.keys()
114 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
115 self.encoding = encoding
117 # Better just be documenting the API
122 self.config = Config(config)
124 # Initialize database connection
125 if self.config.PLC_DB_TYPE == "postgresql":
126 from PLC.PostgreSQL import PostgreSQL
127 self.db = PostgreSQL(self)
130 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
132 def callable(self, method):
134 Return a new instance of the specified method.
138 if method not in self.all_methods:
139 raise PLCInvalidAPIMethod, method
141 # Get new instance of method
143 classname = method.split(".")[-1]
144 if method in self.native_methods:
145 fullpath="PLC.Methods." + method
147 fullpath=self.other_methods_map[method]
148 module = __import__(fullpath, globals(), locals(), [classname])
149 return getattr(module, classname)(self)
150 except ImportError, AttributeError:
151 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
153 def call(self, source, method, *args):
155 Call the named method from the specified source with the
159 function = self.callable(method)
160 function.source = source
161 return function(*args)
163 def handle(self, source, data):
165 Handle an XML-RPC or SOAP request from the specified source.
168 # Parse request into method name and arguments
170 interface = xmlrpclib
171 (args, method) = xmlrpclib.loads(data)
172 methodresponse = True
174 if SOAPpy is not None:
176 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
179 # XXX Support named arguments
184 result = self.call(source, method, *args)
185 except PLCFault, fault:
186 # Handle expected faults
187 if interface == xmlrpclib:
189 methodresponse = None
190 elif interface == SOAPpy:
191 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
192 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
195 if interface == xmlrpclib:
196 if not isinstance(result, PLCFault):
198 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
199 elif interface == SOAPpy:
200 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)