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 methods = PLC.Methods.methods
100 # dict {methodname:module}
102 for module in PLC.Legacy.__all__ :
103 for method in getattr(import_deep("PLC.Legacy."+module),"methods"):
104 legacy_map[method]=module
106 all_methods = methods+legacy_map.keys()
108 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
109 self.encoding = encoding
111 # Better just be documenting the API
116 self.config = Config(config)
118 # Initialize database connection
119 if self.config.PLC_DB_TYPE == "postgresql":
120 from PLC.PostgreSQL import PostgreSQL
121 self.db = PostgreSQL(self)
124 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
126 def callable(self, method):
128 Return a new instance of the specified method.
132 if method not in self.all_methods:
133 raise PLCInvalidAPIMethod, method
135 # Get new instance of method
137 classname = method.split(".")[-1]
138 if method in self.methods:
139 module = __import__("PLC.Methods." + method, globals(), locals(), [classname])
140 return getattr(module, classname)(self)
142 modulename=self.legacy_map[method]
143 module = __import__("PLC.Legacy." + modulename, globals(), locals(), [classname])
144 return getattr(module, classname)(self)
145 except ImportError, AttributeError:
146 raise PLCInvalidAPIMethod, method
148 def call(self, source, method, *args):
150 Call the named method from the specified source with the
154 function = self.callable(method)
155 function.source = source
156 return function(*args)
158 def handle(self, source, data):
160 Handle an XML-RPC or SOAP request from the specified source.
163 # Parse request into method name and arguments
165 interface = xmlrpclib
166 (args, method) = xmlrpclib.loads(data)
167 methodresponse = True
169 if SOAPpy is not None:
171 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
174 # XXX Support named arguments
179 result = self.call(source, method, *args)
180 except PLCFault, fault:
181 # Handle expected faults
182 if interface == xmlrpclib:
184 methodresponse = None
185 elif interface == SOAPpy:
186 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
187 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
190 if interface == xmlrpclib:
191 if not isinstance(result, PLCFault):
193 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
194 elif interface == SOAPpy:
195 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)