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
19 # See "2.2 Characters" in the XML specification:
21 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
23 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
25 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
26 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
28 def xmlrpclib_escape(s, replace = string.replace):
30 xmlrpclib does not handle invalid 7-bit control characters. This
31 function augments xmlrpclib.escape, which by default only replaces
32 '&', '<', and '>' with entities.
35 # This is the standard xmlrpclib.escape function
36 s = replace(s, "&", "&")
37 s = replace(s, "<", "<")
38 s = replace(s, ">", ">",)
40 # Replace invalid 7-bit control characters with '?'
41 return s.translate(xml_escape_table)
43 def xmlrpclib_dump(self, value, write):
45 xmlrpclib cannot marshal instances of subclasses of built-in
46 types. This function overrides xmlrpclib.Marshaller.__dump so that
47 any value that is an instance of one of its acceptable types is
48 marshalled as that type.
50 xmlrpclib also cannot handle invalid 7-bit control characters. See
54 # Use our escape function
55 args = [self, value, write]
56 if isinstance(value, (str, unicode)):
57 args.append(xmlrpclib_escape)
60 # Try for an exact match first
61 f = self.dispatch[type(value)]
63 # Try for an isinstance() match
64 for Type, f in self.dispatch.iteritems():
65 if isinstance(value, Type):
68 raise TypeError, "cannot marshal %s objects" % type(value)
72 # You can't hide from me!
73 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
75 # SOAP support is optional
78 from SOAPpy.Parser import parseSOAPRPC
79 from SOAPpy.Types import faultType
80 from SOAPpy.NS import NS
81 from SOAPpy.SOAPBuilder import buildSOAP
85 from PLC.Config import Config
86 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
102 # other_methods_map : dict {methodname: fullpath}
103 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
105 for subdir in [ 'Accessors' ]:
107 # scan e.g. PLC.Accessors.__all__
108 pkg = __import__(path).__dict__[subdir]
109 for modulename in getattr(pkg,"__all__"):
110 fullpath=path+"."+modulename
111 for method in getattr(import_deep(fullpath),"methods"):
112 other_methods_map[method] = fullpath
114 all_methods = native_methods + other_methods_map.keys()
116 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
117 self.encoding = encoding
119 # Better just be documenting the API
124 self.config = Config(config)
126 # Initialize database connection
127 if self.config.PLC_DB_TYPE == "postgresql":
128 from PLC.PostgreSQL import PostgreSQL
129 self.db = PostgreSQL(self)
131 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
133 # Aspects modify the API injecting code before/after method
134 # calls. As of now we only have aspects for OMF integration,
135 # that's why we enable aspects only if PLC_OMF is set to true.
136 if self.config.PLC_OMF_ENABLED:
137 from aspects import apply_aspects; apply_aspects()
140 def callable(self, method):
142 Return a new instance of the specified method.
146 if method not in self.all_methods:
147 raise PLCInvalidAPIMethod, method
149 # Get new instance of method
151 classname = method.split(".")[-1]
152 if method in self.native_methods:
153 fullpath="PLC.Methods." + method
155 fullpath=self.other_methods_map[method]
156 module = __import__(fullpath, globals(), locals(), [classname])
157 return getattr(module, classname)(self)
158 except ImportError, AttributeError:
159 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
161 def call(self, source, method, *args):
163 Call the named method from the specified source with the
167 function = self.callable(method)
168 function.source = source
169 return function(*args)
171 def handle(self, source, data):
173 Handle an XML-RPC or SOAP request from the specified source.
176 # Parse request into method name and arguments
178 interface = xmlrpclib
179 (args, method) = xmlrpclib.loads(data)
180 methodresponse = True
182 if SOAPpy is not None:
184 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
187 # XXX Support named arguments
192 result = self.call(source, method, *args)
193 except PLCFault, fault:
194 # Handle expected faults
195 if interface == xmlrpclib:
197 methodresponse = None
198 elif interface == SOAPpy:
199 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
200 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
203 if interface == xmlrpclib:
204 if not isinstance(result, PLCFault):
206 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
207 elif interface == SOAPpy:
208 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
212 def handle_json(self, source, data):
214 Handle a JSON request
216 method, args = simplejson.loads(data)
218 result = self.call(source, method, *args)
222 return simplejson.dumps(result)