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 # Try to use jsonlib before using simpljson. This is a hack to get around
20 # the fact that the version of simplejson avaialble for f8 is slightly
21 # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib
22 # pacakge available for f8, so this has to be installed manually and
23 # is not expected to always be available. Remove this once we move away
24 # from f8 based MyPLC's
30 # See "2.2 Characters" in the XML specification:
32 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
34 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
36 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
37 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
39 def xmlrpclib_escape(s, replace = string.replace):
41 xmlrpclib does not handle invalid 7-bit control characters. This
42 function augments xmlrpclib.escape, which by default only replaces
43 '&', '<', and '>' with entities.
46 # This is the standard xmlrpclib.escape function
47 s = replace(s, "&", "&")
48 s = replace(s, "<", "<")
49 s = replace(s, ">", ">",)
51 # Replace invalid 7-bit control characters with '?'
52 return s.translate(xml_escape_table)
54 def xmlrpclib_dump(self, value, write):
56 xmlrpclib cannot marshal instances of subclasses of built-in
57 types. This function overrides xmlrpclib.Marshaller.__dump so that
58 any value that is an instance of one of its acceptable types is
59 marshalled as that type.
61 xmlrpclib also cannot handle invalid 7-bit control characters. See
65 # Use our escape function
66 args = [self, value, write]
67 if isinstance(value, (str, unicode)):
68 args.append(xmlrpclib_escape)
71 # Try for an exact match first
72 f = self.dispatch[type(value)]
74 # Try for an isinstance() match
75 for Type, f in self.dispatch.iteritems():
76 if isinstance(value, Type):
79 raise TypeError, "cannot marshal %s objects" % type(value)
83 # You can't hide from me!
84 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
86 # SOAP support is optional
89 from SOAPpy.Parser import parseSOAPRPC
90 from SOAPpy.Types import faultType
91 from SOAPpy.NS import NS
92 from SOAPpy.SOAPBuilder import buildSOAP
96 from PLC.Config import Config
97 from PLC.Faults import *
101 def import_deep(name):
102 mod = __import__(name)
103 components = name.split('.')
104 for comp in components[1:]:
105 mod = getattr(mod, comp)
110 # flat list of method names
111 native_methods = PLC.Methods.native_methods
113 # other_methods_map : dict {methodname: fullpath}
114 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
116 for subdir in [ 'Accessors' ]:
118 # scan e.g. PLC.Accessors.__all__
119 pkg = __import__(path).__dict__[subdir]
120 for modulename in getattr(pkg,"__all__"):
121 fullpath=path+"."+modulename
122 for method in getattr(import_deep(fullpath),"methods"):
123 other_methods_map[method] = fullpath
125 all_methods = native_methods + other_methods_map.keys()
127 def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
128 self.encoding = encoding
130 # Better just be documenting the API
135 self.config = Config(config)
137 # Initialize database connection
138 if self.config.PLC_DB_TYPE == "postgresql":
139 from PLC.PostgreSQL import PostgreSQL
140 self.db = PostgreSQL(self)
142 raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
144 # Aspects modify the API by injecting code before, after or
145 # around method calls. -- http://github.com/baris/pyaspects/blob/master/README
147 # As of now we only have aspects for OMF integration, that's
148 # why we enable aspects only if PLC_OMF is set to true.
149 if self.config.PLC_OMF_ENABLED:
150 from aspects import apply_omf_aspect
154 def callable(self, method):
156 Return a new instance of the specified method.
160 if method not in self.all_methods:
161 raise PLCInvalidAPIMethod, method
163 # Get new instance of method
165 classname = method.split(".")[-1]
166 if method in self.native_methods:
167 fullpath="PLC.Methods." + method
169 fullpath=self.other_methods_map[method]
170 module = __import__(fullpath, globals(), locals(), [classname])
171 return getattr(module, classname)(self)
172 except ImportError, AttributeError:
173 raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
175 def call(self, source, method, *args):
177 Call the named method from the specified source with the
181 function = self.callable(method)
182 function.source = source
183 return function(*args)
185 def handle(self, source, data):
187 Handle an XML-RPC or SOAP request from the specified source.
190 # Parse request into method name and arguments
192 interface = xmlrpclib
193 (args, method) = xmlrpclib.loads(data)
194 methodresponse = True
196 if SOAPpy is not None:
198 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
201 # XXX Support named arguments
206 result = self.call(source, method, *args)
207 except PLCFault, fault:
208 # Handle expected faults
209 if interface == xmlrpclib:
211 methodresponse = None
212 elif interface == SOAPpy:
213 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
214 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
217 if interface == xmlrpclib:
218 if not isinstance(result, PLCFault):
220 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
221 elif interface == SOAPpy:
222 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
226 def handle_json(self, source, data):
228 Handle a JSON request
230 method, args = json.loads(data)
232 result = self.call(source, method, *args)
236 return json.dumps(result)