1 | # Copyright (C) 2007 STFC & NERC (Science and Technology Facilities Council). |
---|
2 | # This software may be distributed under the terms of the |
---|
3 | # Q Public License, version 1.0 or later. |
---|
4 | # http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt |
---|
5 | """ |
---|
6 | Manages interface to NDG documents and data, including caching |
---|
7 | """ |
---|
8 | |
---|
9 | from cache import Cache |
---|
10 | import os, tempfile |
---|
11 | import cdms, csml |
---|
12 | from ows_server.models import ndgRetrieve, ndgObject |
---|
13 | from pylons import request,session |
---|
14 | import logging |
---|
15 | logger = logging.getLogger('ndgInterface') |
---|
16 | from ows_server.models.ndgSecurity import HandleSecurity |
---|
17 | |
---|
18 | class CDMSEntry(object): |
---|
19 | """ |
---|
20 | A reference to a CDMS file that will delete the file when it is |
---|
21 | garbage collected. |
---|
22 | |
---|
23 | Instances of this class are used as Cache entry values. When entries are |
---|
24 | automatically removed from the cache the CDMSEntry object will be |
---|
25 | garbage collected unless it is being accessed by another thread. |
---|
26 | |
---|
27 | """ |
---|
28 | |
---|
29 | __slots__ = ['_f', 'var'] |
---|
30 | |
---|
31 | def __init__(self, filename, varname): |
---|
32 | logger.info('Caching file variable %s in %s' % (varname, filename)) |
---|
33 | self._f = cdms.open(filename) |
---|
34 | self.var = self._f[varname] |
---|
35 | def __del__(self): |
---|
36 | filename = self._f.id |
---|
37 | logger.info('Removing file %s' % filename) |
---|
38 | self._f.close() |
---|
39 | os.remove(filename) |
---|
40 | |
---|
41 | |
---|
42 | class CSMLExtractCache(Cache): |
---|
43 | |
---|
44 | def __init__(self, cache_dir, max_size=0): |
---|
45 | super(CSMLExtractCache, self).__init__(max_size) |
---|
46 | self._cache_dir = cache_dir |
---|
47 | |
---|
48 | def _extract(self, feature, sel): |
---|
49 | (fd, filename) = tempfile.mkstemp('.nc', 'csml_wxs_', self._cache_dir) |
---|
50 | os.close(fd) |
---|
51 | (outputdir, ncname) = os.path.split(filename) |
---|
52 | |
---|
53 | # Workarround until ticket:778 (TestExtractAll) is fixed |
---|
54 | sel.update(longitude=(-180, 180), latitude=(-90, 90)) |
---|
55 | |
---|
56 | feature.subsetToGridSeries(ncname=ncname, outputdir=outputdir, **sel) |
---|
57 | |
---|
58 | return filename |
---|
59 | |
---|
60 | def key(self, name): |
---|
61 | """ |
---|
62 | Cache provides this method to map externally visible entry names |
---|
63 | to internal keys. We use it here to turn selectors into hashable |
---|
64 | values. |
---|
65 | |
---|
66 | """ |
---|
67 | (feature, sel) = name |
---|
68 | |
---|
69 | return (feature.id, tuple(sorted(sel.items()))) |
---|
70 | |
---|
71 | def build(self, key, name, opened, entry): |
---|
72 | (feature, sel) = name |
---|
73 | filename = self._extract(feature, sel) |
---|
74 | |
---|
75 | return CDMSEntry(filename, feature.name.CONTENT) |
---|
76 | |
---|
77 | def __getitem__(self, name): |
---|
78 | """ |
---|
79 | Returns the opened CDMS object. |
---|
80 | |
---|
81 | """ |
---|
82 | return super(CSMLExtractCache, self).__getitem__(name).var |
---|
83 | |
---|
84 | class ndgInterface: |
---|
85 | |
---|
86 | def __init__(self): |
---|
87 | ''' Instantiate with three level cache: |
---|
88 | - a file cache |
---|
89 | - a xmlhandler object cache, and |
---|
90 | - a parsed CSML object cache.''' |
---|
91 | |
---|
92 | self.CSMLDataCache=CSMLExtractCache( |
---|
93 | request.environ['paste.config']['app_conf']['tmp_dir'],max_size=10) |
---|
94 | self.CSMLDocCache=Cache(max_size=10) |
---|
95 | self.XMLHCache=Cache(max_size=10) |
---|
96 | |
---|
97 | |
---|
98 | def GetXML(self,uri,format=''): |
---|
99 | ''' This method provides a secure interface to the server |
---|
100 | document cache and a remote NDG exist. It is assumed that |
---|
101 | the local filesystem is protected in that you can't get to |
---|
102 | files except via the CSML api ''' |
---|
103 | # Note that this method should not be used to obtain |
---|
104 | #unsecured discovery documents, these are called directly |
---|
105 | #in the retrieve controller! |
---|
106 | |
---|
107 | try: |
---|
108 | ndgO=ndgObject.ndgObject(uri) |
---|
109 | localFile=0 |
---|
110 | except ValueError: |
---|
111 | ''' It's a local file not an ndg identifier ''' |
---|
112 | ndg0=uri |
---|
113 | localFile=1 |
---|
114 | |
---|
115 | if 'ndgCleared' in session: |
---|
116 | cleared=session['ndgCleared'] |
---|
117 | else: |
---|
118 | cleared=None |
---|
119 | |
---|
120 | try: |
---|
121 | xmlh=self.XMLHCache[uri] |
---|
122 | status=1 |
---|
123 | logging.info('XMLH Cache hit for [%s]'%uri) |
---|
124 | except: |
---|
125 | logging.info('XMLH Cache miss for [%s]'%uri) |
---|
126 | if localFile: |
---|
127 | status,xmlH=self.__getLocal(uri) |
---|
128 | else: |
---|
129 | status,xmlh=ndgRetrieve.ndgRetrieve(ndgO,request.environ['ndgConfig'],logger,format) |
---|
130 | if status: self.XMLHCache[uri]=xmlh |
---|
131 | |
---|
132 | # valid values of the return objects SHOULD BE |
---|
133 | # ok: status=1, xmlh=an xml handler instance. |
---|
134 | # exceptions, status=0, xmlh='Exception(e)' |
---|
135 | |
---|
136 | status,xmlh=self.__GateKeep(ndgO,xmlh) |
---|
137 | if status: |
---|
138 | if cleared is None: |
---|
139 | session['ndgCleared']=[uri] |
---|
140 | else: |
---|
141 | session['ndgCleared'].append(uri) |
---|
142 | session.save() |
---|
143 | |
---|
144 | return status,xmlh |
---|
145 | |
---|
146 | def GetParsedCSML(self,uri): |
---|
147 | |
---|
148 | ''' This method gets a parsed CSML object corresponding to the URI ''' |
---|
149 | |
---|
150 | # do we need an xml handler instance to test the security? |
---|
151 | if 'ndgCleared' not in session: |
---|
152 | status,xmlh=self.GetXML(uri) |
---|
153 | else: |
---|
154 | if uri not in session['ndgCleared']: |
---|
155 | status,xmlh=self.GetXML(uri) |
---|
156 | else: status=1 |
---|
157 | if not status: return status,xmlh |
---|
158 | |
---|
159 | try: |
---|
160 | d=self.CSMLDocCache[uri] |
---|
161 | logging.info('CSML Cache hit for [%s]'%uri) |
---|
162 | except: |
---|
163 | logging.info('CSML Cache miss for [%s]'%uri) |
---|
164 | status,xmlh=self.GetXML(uri) |
---|
165 | if not status: return status,xmlh |
---|
166 | d=csml.parser.Dataset() |
---|
167 | d.parseElemTree(xmlh.tree) |
---|
168 | self.CSMLDocCache[uri]=d |
---|
169 | status=1 |
---|
170 | return status,d |
---|
171 | |
---|
172 | def __GateKeep(self,uri,x): |
---|
173 | ''' This is the NDG gatekeeper ''' |
---|
174 | if 'ndgSec' in session: |
---|
175 | securityTokens=session['ndgSec'] |
---|
176 | else: |
---|
177 | securityTokens=None |
---|
178 | |
---|
179 | if uri.schema=='DIF': |
---|
180 | pass # no access control |
---|
181 | elif uri.schema =='NDG-B0': |
---|
182 | #cred=x.find('dgSecurityCondition/simpleCondition') |
---|
183 | #if cred: |
---|
184 | # return 0,'<p> Access Control: <br/>[<![CDAT[%s]]> </p>' |
---|
185 | pass |
---|
186 | elif uri.schema =='NDG-B1': |
---|
187 | pass # for the moment |
---|
188 | elif uri.schema =='NDG-A0': |
---|
189 | s=x.tree.find('{http://ndg.nerc.ac.uk/csml}AccessControlPolicy/{http://ndg.nerc.ac.uk/csml}dgSecurityCondition') |
---|
190 | if s is not None: |
---|
191 | status,message=HandleSecurity(s,securityTokens) |
---|
192 | if not status: return 0,'<p> Access Denied </p><p>%s</p>'%message |
---|
193 | return 1,x |
---|
194 | |
---|
195 | def __getLocal(self,uri): |
---|
196 | ''' Returns a local csml file (used for testing) ''' |
---|
197 | csml_dir = request.environ['paste.config']['app_conf']['csml_dir'] |
---|
198 | path = os.path.join(csml_dir, file) |
---|
199 | if os.path.exists(path+'.csml'): |
---|
200 | f = path+'.csml' |
---|
201 | elif os.path.exists(path+'.xml'): |
---|
202 | f = path +'.xml' |
---|
203 | else: |
---|
204 | return 0, '<p>Cannot find CSML file %s</p>' % file |
---|
205 | r=f.read() |
---|
206 | return 1,r |
---|
207 | |
---|
208 | |
---|
209 | |
---|
210 | interface=ndgInterface() |
---|