store connections to federated registries and aggregates in the api object
[sfa.git] / sfa / util / api.py
1 #
2 # SFA XML-RPC and SOAP interfaces
3 #
4 ### $Id$
5 ### $URL$
6 #
7
8 import sys
9 import os
10 import traceback
11 import string
12 import xmlrpclib
13
14 from sfa.trust.auth import Auth
15 from sfa.util.config import *
16 from sfa.util.faults import *
17 from sfa.util.debug import *
18 from sfa.trust.credential import *
19 from sfa.trust.certificate import *
20 from sfa.util.namespace import *
21 from sfa.util.sfalogging import *
22 from sfa.server.registry import Registries
23 from sfa.server.aggregate import Aggregates
24
25 # See "2.2 Characters" in the XML specification:
26 #
27 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
28 # avoiding
29 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
30
31 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
32 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
33
34 def xmlrpclib_escape(s, replace = string.replace):
35     """
36     xmlrpclib does not handle invalid 7-bit control characters. This
37     function augments xmlrpclib.escape, which by default only replaces
38     '&', '<', and '>' with entities.
39     """
40
41     # This is the standard xmlrpclib.escape function
42     s = replace(s, "&", "&amp;")
43     s = replace(s, "<", "&lt;")
44     s = replace(s, ">", "&gt;",)
45
46     # Replace invalid 7-bit control characters with '?'
47     return s.translate(xml_escape_table)
48
49 def xmlrpclib_dump(self, value, write):
50     """
51     xmlrpclib cannot marshal instances of subclasses of built-in
52     types. This function overrides xmlrpclib.Marshaller.__dump so that
53     any value that is an instance of one of its acceptable types is
54     marshalled as that type.
55
56     xmlrpclib also cannot handle invalid 7-bit control characters. See
57     above.
58     """
59
60     # Use our escape function
61     args = [self, value, write]
62     if isinstance(value, (str, unicode)):
63         args.append(xmlrpclib_escape)
64
65     try:
66         # Try for an exact match first
67         f = self.dispatch[type(value)]
68     except KeyError:
69         raise
70         # Try for an isinstance() match
71         for Type, f in self.dispatch.iteritems():
72             if isinstance(value, Type):
73                 f(*args)
74                 return
75         raise TypeError, "cannot marshal %s objects" % type(value)
76     else:
77         f(*args)
78
79 # You can't hide from me!
80 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
81
82 # SOAP support is optional
83 try:
84     import SOAPpy
85     from SOAPpy.Parser import parseSOAPRPC
86     from SOAPpy.Types import faultType
87     from SOAPpy.NS import NS
88     from SOAPpy.SOAPBuilder import buildSOAP
89 except ImportError:
90     SOAPpy = None
91
92
93 def import_deep(name):
94     mod = __import__(name)
95     components = name.split('.')
96     for comp in components[1:]:
97         mod = getattr(mod, comp)
98     return mod
99
100 class BaseAPI:
101
102     def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", methods='sfa.methods',
103
104                  peer_cert = None, interface = None, key_file = None, cert_file = None):
105
106         self.encoding = encoding
107         
108         # flat list of method names
109          
110         self.methods_module = methods_module = __import__(methods, fromlist=[methods])
111         self.methods = methods_module.all
112
113         # Better just be documenting the API
114         if config is None:
115             return
116         
117         # Load configuration
118         self.config = Config(config)
119         self.auth = Auth(peer_cert)
120         self.hrn = self.config.SFA_INTERFACE_HRN
121         self.interface = interface
122         self.key_file = key_file
123         self.key = Keypair(filename=self.key_file)
124         self.cert_file = cert_file
125         self.cert = Certificate(filename=self.cert_file)
126         self.credential = None
127         self.source = None 
128         self.time_format = "%Y-%m-%d %H:%M:%S"
129         self.logger=get_sfa_logger()
130         
131         # load registries
132         self.registries = Registries(self) 
133
134         # load aggregates
135         self.aggregates = Aggregates(self)
136
137
138     def callable(self, method):
139         """
140         Return a new instance of the specified method.
141         """
142         # Look up method
143         if method not in self.methods:
144             raise SfaInvalidAPIMethod, method
145         
146         # Get new instance of method
147         try:
148             classname = method.split(".")[-1]
149             module = __import__(self.methods_module.__name__ + "." + method, globals(), locals(), [classname])
150             callablemethod = getattr(module, classname)(self)
151             return getattr(module, classname)(self)
152         except ImportError, AttributeError:
153             raise SfaInvalidAPIMethod, method
154
155     def call(self, source, method, *args):
156         """
157         Call the named method from the specified source with the
158         specified arguments.
159         """
160         function = self.callable(method)
161         function.source = source
162         self.source = source
163         return function(*args)
164
165     def handle(self, source, data):
166         """
167         Handle an XML-RPC or SOAP request from the specified source.
168         """
169         # Parse request into method name and arguments
170         try:
171             interface = xmlrpclib
172             (args, method) = xmlrpclib.loads(data)
173             methodresponse = True
174         except Exception, e:
175             if SOAPpy is not None:
176                 interface = SOAPpy
177                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
178                 method = r._name
179                 args = r._aslist()
180                 # XXX Support named arguments
181             else:
182                 raise e
183
184         try:
185             result = self.call(source, method, *args)
186         except Exception, fault:
187             traceback.print_exc(file = log)
188             # Handle expected faults
189             if interface == xmlrpclib:
190                 result = fault
191                 methodresponse = None
192             elif interface == SOAPpy:
193                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
194                 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
195             else:
196                 raise
197
198         # Return result
199         if interface == xmlrpclib:
200             if not isinstance(result, SfaFault):
201                 result = (result,)
202
203             data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
204         elif interface == SOAPpy:
205             data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
206
207         return data
208