source: TI03-DataExtractor/trunk/cgi/dxui @ 1244

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI03-DataExtractor/trunk/cgi/dxui@1244
Revision 1244, 19.3 KB checked in by astephen, 13 years ago (diff)

Close to alpha version.

  • Property svn:executable set to *
Line 
1#!<YOUR_PYTHON_HERE>
2#   Copyright (C) 2004 CCLRC & NERC( Natural Environment Research Council ).
3#   This software may be distributed under the terms of the
4#   Q Public License, version 1.0 or later. http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5
6"""
7dxui
8====
9
10Holds the CGIClient class the users interact with if they
11are calling the DX web service via a CGI interface through a
12web browser. Typically this script will be called "dxui".
13
14"""
15
16# Import standard library modules
17import cgi, random, time, re, os, sys
18
19# Import SOAP library
20from ZSI.client import Binding
21from ZSI.version import Version as zsiv
22zsiv=float("%s.%s" % zsiv[:2])
23       
24# Get configuration file for this dx client
25configFile="<PLACE_CONFIG_PATH_HERE>"
26configPath, configFilename=os.path.split(configFile)
27sys.path.insert(0, configPath)
28exec ("from %s import *" % configFilename[:-3]) 
29         
30# Import package modules
31from pydxc import *
32
33# Set up CGI error reporting if DEBUG is set to 1 in clientConfig.py module
34if DEBUG==1:
35    import cgitb
36    cgitb.enable()
37
38
39class DXCGIClient:
40    """
41    The controlling class for interacting with the dx via the web.
42    """
43   
44    def __init__(self):
45        """
46        Initiates the instance setting up the appropriate
47        internal objects and calls to the relevant classes depending
48        on the configuration and arguments provided.
49        """
50        self.username="undefined"
51        self.password="undefined"
52        self.secureToken="undefined"
53        self.userRoles=[]
54        self.loginStatus="out"
55        self.loginMessage=""
56        self.sessionObject={}
57       
58        self.displayer=DisplayManager()
59
60        # Parse the arguments
61        try:
62            self._parseArgs()
63        except:
64            if DEBUG==1:
65                raise
66            else:
67                CGIErrorHandler("Error parsing arguments: "+str(sys.exc_type), noheader=0)
68
69        # Destroy any arguments not intended to pass any further
70        try:
71            self._destroyUnnecessaryArgs()
72        except:
73            if DEBUG==1:
74                raise
75            else:
76                CGIErrorHandler("Error destroying arguments: "+str(sys.exc_type), noheader=0)
77
78        # If switched on check security       
79        if RESTRICTED_DATA==1:
80            try:
81                self._checkSecurity()
82            except:
83                if DEBUG==1:
84                    raise
85                else:
86                    CGIErrorHandler("Security error: "+str(sys.exc_type), noheader=0)
87        else:
88            self.secureToken="undefined"
89           
90        # Call the dx Web Service with the arguments provided       
91        try:
92            self._callDXWebService(self.args)   
93        except SystemExit:
94            pass         
95        except:
96            if DEBUG==1:
97                raise
98            else:
99                CGIErrorHandler("Error calling dx Web Service: "+str(sys.exc_type), noheader=0)       
100
101        try:   
102            self.displayer._displayHTMLFooter()
103        except:
104            if DEBUG==1:
105                raise
106            else:
107                CGIErrorHandler("Error generating HTML footer: "+str(sys.exc_type), noheader=0)
108       
109       
110    def _parseArgs(self):
111        """
112        Parses the arguments sent, if any, ready for sending to the
113        web service.
114        """
115        # Get arguments
116        self.args={}                           
117        args=cgi.FieldStorage()
118        self.fieldStorage=args
119       
120        # Get upload file link
121        if args.has_key("uploadedFile"):
122            self.tempFileLink=args["uploadedFile"].file
123            self.args["fileName"]=args["uploadedFile"].filename
124
125        # Populate the argument dictionary
126        for key in args.keys():
127            if key=="uploadedFile": continue
128            self.args[key]=args.getvalue(key)
129                   
130        if self.args.has_key("sessionID") and self.args["sessionID"]=="undefined":
131            self.args["sessionID"]="undefined"     
132         
133        # Check for secure items, destroy if necessary and assign as instance variables   
134        if self.args.has_key("yousirnaim"):
135            self.username=self.args["yousirnaim"]
136            self.args["username"]=self.username
137            del self.args["yousirnaim"]
138           
139        if self.args.has_key("parcewerd"):
140            self.password=self.args["parcewerd"]
141            del self.args["parcewerd"] 
142               
143        if self.args.has_key("secureToken"):
144            self.secureToken=self.args["secureToken"]
145            del self.args["secureToken"]   
146       
147        if not self.args.has_key("username"):
148            self.args["username"]="undefined"
149           
150        #self._determineNumberOfDatasets()
151        #self._parsePartialDimensionArgs()
152       
153        self._compileAxisSelections()
154       
155
156    def _compileAxisSelections(self):
157        """
158        Compiles partial selections from the last form selections
159        so that axis start and end components are linked into a
160        list of arguments and string components become whole date/time
161        strings.
162        """
163        #print "Content-Type: text/html\n\n"
164        #print "<P>".join(["%s:%s" % (n,v) for (n,v) in self.args.items()])     
165        dateTimeTemplate="%.4d-%.2d-%.2dT%.2d:%.2d:%f"
166        timeAxisItems=getDictSubsetMatching(self.args, "axis_.*_low\.time\.year", "regex")
167       
168        # Must have come from dxui so if year is there all others will be
169        for name, value in timeAxisItems.items():
170            axisIndex=re.match(r"axis_(.*)_low\.time\.year", name).groups()[0]
171            tcList=[]
172            for lh in ("low", "high"):
173                for t in "year month day hour minute second".split():
174                    argName="axis_%s_%s.time.%s" % (axisIndex, lh, t)
175                    tcList.append(self.args[argName])
176                    del self.args[argName]
177       
178            # Now compile into start and end date time
179            floatList=[float(i) for i in tcList]
180            self.args["axis_%s" % axisIndex]=[dateTimeTemplate % tuple(floatList[:6]),
181                                              dateTimeTemplate % tuple(floatList[6:])]
182
183        otherAxisItems=getDictSubsetMatching(self.args, "axis_.*_low$", "regex")
184        for name, value in otherAxisItems.items():
185            axisIndex=re.match(r"axis_(.*)_low$", name).groups()[0]
186            iList=[]
187            for lh in ("low", "high"):
188                argName="axis_%s_%s" % (axisIndex, lh)
189                iList.append(self.args[argName])
190                del self.args[argName]
191       
192            try:
193                if zsiv>=2.0:
194                    iList=[float(i) for i in iList]
195                else:
196                    iList=[int(i) for i in iList]
197            except:
198                try:
199                    iList=[int(i) for i in iList]
200                except:
201                    pass
202           
203            self.args["axis_%s" % axisIndex]=iList
204       
205        #print "Content-Type: text/html\n\n"
206        #print "<P>".join(["%s:%s" % (n,v) for (n,v) in self.args.items()])
207       
208
209    def _destroyUnnecessaryArgs(self):
210        """
211        Destroy the arguments that should not be passed to the main dx Web Service.
212        """
213        if self.args.has_key("password"):
214            del self.args["password"]   
215       
216       
217    def _checkSecurity(self):
218        """
219        If security is switched on with RESTRICTED_DATA=1 then this will
220        call the local implementation of the security.
221        """
222        if SECURITY_MODEL=="basic":
223            secChecker=SecurityViaCGI(self.username, self.password, self.secureToken)
224       
225            # Deal with logout
226            ############# NOTE - doesn't destroy session server side (yet)
227            if self.args.has_key("logout") and self.args["logout"]=="Logout":
228                secChecker.logout()
229                secCheck="You have been logged out."
230            else:
231                secCheck=secChecker.validate()     
232           
233            if type(secCheck)==type(""):
234                # Returned string means error in log in or logged out
235                self.loginMessage=secCheck
236            else:
237                self.loginStatus="in"
238                self.loginMessage=""
239                (self.secureToken, self.username, self.userRoles)=secCheck
240               
241        elif SECURITY_MODEL=="ndg":
242            from pydxc.NDGSecurityViaCGI import *
243            secChecker=NDGSecurityViaCGI(cookie=self.secureToken, urlArgs=self.fieldStorage)
244            self.loginlist=secChecker.getTrustedHostList()
245           
246            #(self.secureToken, self.username, self.userRoles)=
247            secCheck=secChecker.validate()
248            if type(secCheck)==type(""):
249                self.loginMessage=secCheck
250            else:
251                self.loginStatus="in"
252                self.loginMessage=""
253                (self.secureToken, self.username, self.userRoles)=secCheck
254
255            #o=open("/tmp/t", "w"); o.write("%s" % repr(secCheck)); o.close()
256           
257            # Delete field storage so not visible anywhere
258            #del self.fieldStorage
259
260               
261    def _callDXWebService(self, args):
262        """
263        According to the arguments given this binds to an appropriate
264        Web Service and calls it with the relevant arguments.
265        """
266        #print "Content-Type: text/html\n\n"
267        # Just print the login page if not logged in and login required
268        if RESTRICTED_DATA==1 and self.loginStatus=="out":
269            self.displayer._displayHTTPHeader()
270            self.displayer._displayHTMLHeader()
271            #print "FIELD STORAGE:", self.fieldStorage
272            self.displayer._displayIntroduction()
273            #print self.loginlist           
274            if SECURITY_MODEL=="basic":
275                self.displayer._displayLoginBar(self.username, self.loginStatus, self.loginMessage)
276            elif SECURITY_MODEL=="ndg":
277                argString="?"
278                for key in self.fieldStorage.keys():
279                  if key.find("datasetURI_")>-1:
280                    if argString!="?":
281                        gapper="&"
282                    else:
283                        gapper=""
284                    argString=argString+("%s%s=%s" % (gapper, key, self.fieldStorage.getvalue(key)))
285                    if argString=="?": argString=""
286                self.displayer._displayNDGLoginBar(self.loginlist, argString)
287                # Delete field storage so not visible anywhere
288                del self.fieldStorage
289            return     
290                             
291        # Set up SOAP bindings 
292        if CALL_METHOD.upper()=="WS":
293            self.server=WSCaller()
294        elif CALL_METHOD.upper()=="LOCAL":
295            serverLocation=LOCAL_SERVER_PACKAGE
296            sys.path.append(serverLocation)
297            print sys.path
298            self.server=LocalCaller()
299
300        # Get session ID
301        self._getSession()
302       
303        # Check if complete
304        isCompleteStatus=self._checkIfComplete()
305       
306
307        # Perform actions as requested by user
308        if self.args.has_key("action"):
309            actionReturn=self._performActions()
310            if actionReturn=="Display footer then complete":
311                return
312       
313        # Clear request if required
314        if self.args.has_key("clearRequest") or self.args.has_key("newRequest"):
315            if self.args.has_key("clearRequest"):
316                del self.args["clearRequest"]
317            else:
318                del self.args["newRequest"] 
319            self.sessionID, self.secureToken=self.server.callServerMethod("newRequest",
320                                                  [self.sessionID, self.secureToken])                                           
321
322        # Make sure instance and args sessionID are the same
323        self.args["sessionID"]=self.sessionID
324       
325        # Ensure args contains secureToken object
326        if not self.args.has_key("secureToken"):
327            self.args["secureToken"]=self.secureToken
328
329        # Get the options (and make selections if appropriate)
330        (optionCategories, options, optionStrings, summaryString, secureToken)=self._getLatestOptions()
331       
332        # Get summary of request and determine number of datasets   
333        #self.summary=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
334           
335        # Analyse the request
336        self._respondToOptionCategories(optionCategories, options, optionStrings, summaryString)       
337   
338       
339    def _getSession(self):
340        """
341        Checks if a session is already underway and starts one if not.
342        """           
343        # If no session then start a session and get a sessionID
344        if not self.args.has_key("sessionID") or self.args["sessionID"] in (None, "None", "undefined"):
345            # Start session if not known about
346            if zsiv>=2.0:
347                self.sessionID, self.secureToken=self.server.callServerMethod("startSession", [self.username, self.password, self.secureToken])[0]
348            else:
349                self.sessionID, self.secureToken=self.server.callServerMethod("startSession", [self.username, self.password, self.secureToken])
350        else:
351            self.sessionID=self.args["sessionID"]
352           
353           
354    def _checkIfComplete(self):
355        """
356        Checks if complete, returns 1 (yes) or 0 (no).
357        """
358        isCompleteStatus=0       
359        if self.args.has_key("isComplete"):     
360            isCompleteStatus, self.secureToken=self.server.callServerMethod("isComplete",
361                                                      [self.sessionID, self.secureToken])     
362        return isCompleteStatus
363       
364       
365    def _performActions(self):
366        """
367        If "action" argument received then do the appropriate action.
368        """     
369        if 1:  # Move all back <-- one tab and delete this line
370            action=self.args["action"]
371            if action=="viewRequestSummary":
372                summaryString=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
373                if zsiv>=2.0:
374                    summaryString=summaryString[0][0]
375
376                self.displayer._displayHTMLHeader()
377                if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
378                self.displayer._displayRequestSummaryTable(createSummaryDict(summaryString))
379                self.displayer._displayReturnLine(self.sessionID)
380                return "Display footer then complete"
381               
382            elif action=="saveRequest":
383                x=self.server.callServerMethod("getDataSubsetSpecifier",
384                                                           [self.sessionID, self.secureToken])
385
386                #dataSubsetSpecifier, self.secureToken=x                                           
387                self.displayer._displayHTTPHeader()
388                print x         
389                self.displayer._displayHTMLHeader()     
390                if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))       
391                self.displayer._displaySaveRequestOptions(dataSubsetSpecifier, self.sessionID)
392                self.displayer._displayReturnLine(self.sessionID)
393                return "Display footer then complete"
394               
395            elif action=="uploadRequest":
396                self.displayer._displayHTTPHeader()
397                self.displayer._displayHTMLHeader()
398                if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
399                self.displayer._displayUploadRequestOptions(self.sessionID)
400                self.displayer._displayReturnLine(self.sessionID)               
401                return "Display footer then complete"           
402               
403            elif action=="parseUploadedFile":
404                xmlFileString=self.tempFileLink.read()
405                status, self.secureToken=self.server.callServerMethod("uploadRequest",
406                                              [self.sessionID, xmlFileString, self.secureToken])
407       
408         
409    def _getLatestOptions(self):
410        """
411        If target page provided then get the options for that page,
412        otherwise get whatever options are presented by the dx and
413        send latest selections to update request.
414        """
415        if self.args.has_key("targetPage"):
416            tp=self.args["targetPage"]
417            tp2catMap={"DatasetGroupPage":"datasetGroup", "DatasetPage":"dataset",
418                       "VariablesPage":"variable", "DomainAndFormatPage":"domainAndFormat"}
419            optionCategory=tp2catMap[tp]
420            self.args["optionCategoryRequested"]=optionCategory
421            # Need to delete targetPage otherwise it will stay forever
422            del self.args["targetPage"]
423
424        response=self.server.callServerMethod("selectOptions", [self.sessionID, self._packArgsAsList(self.args)])
425        #self.displayer._displayHTTPHeader()
426        (optionCategories, options, optionStrings, summaryString, secureToken)=self._translateResponse(response)       
427        #print (optionCategories, options, optionStrings, summaryString, secureToken)   
428        return (optionCategories, options, optionStrings, summaryString, secureToken)
429
430   
431    def _translateResponse(self, response):
432        """
433        Takes an object received from the WS and translates to:
434        (optionCategories, options, optionStrings, secureToken)
435        """
436        response=response[0]       
437        if zsiv>=2.0:
438            response=response[0][0]
439        optionCategories=[item[0] for item in response[:-2]]
440        options=[item[1] for item in response[:-2]]
441        optionStrings=options
442        summaryString=response[-2]
443        secureToken=response[-1]               
444        return (optionCategories, options, optionStrings, summaryString, secureToken)
445       
446
447    def _respondToOptionCategories(self, optionCategories, options, optionStrings, summaryString):
448        """
449        Work out what the option category is and respond by displaying
450        the appropriate user interface.
451        """     
452        summaryDict=createSummaryDict(summaryString)
453        # Analyse the option categories now
454        if optionCategories=="No category" or optionCategories==[]:
455            optcat=None
456        else:
457            optcat=optionCategories[0].split("_")[0]
458       
459        if optcat==None and (RESTRICTED_DATA==1 and summaryString.find("dataset")<0):
460            self.displayer._displayHTTPHeader()
461            self.displayer._displayHTMLHeader() 
462            print "<P><B>You do not have the credential to view any datasets - Sorry!</B><P>"   
463       
464        # Can display simple form for these categories 
465        elif optcat in ("datasetGroup", "dataset", "variable"):         
466            self.displayer._displayHTTPHeader()
467            self.displayer._displayHTMLHeader()     
468            if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
469            self.displayer._displayDatasetSummaryLine(summaryString, optionCategories[0], self.sessionID)
470            self.displayer._displayMainTableHeader(self.sessionID)
471            self.displayer._displayOptionsTable(summaryDict, optionCategories, options, optionStrings, self.sessionID, self.sessionObject)
472            self.displayer._displayMainTableFooter()
473       
474        # Need to make a number of web service calls to get information
475        # for each domain as well as output format which are all
476        # displayed on the same page.   
477        elif optcat=="axis":
478            self.displayer._displayHTTPHeader()
479            self.displayer._displayHTMLHeader()
480            if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
481            self.displayer._displayDatasetSummaryLine(summaryString, optionCategories[0], self.sessionID)
482            self.displayer._displayMainTableHeader(self.sessionID, onSubmit=""" onSubmit='return validateAllTimeSelections()'""")
483            self.displayer._displayDomainOptions(summaryDict, optionCategories, options, optionStrings, self.sessionID)
484            self.displayer._displayMainTableFooter()
485               
486        elif optcat==None:
487            # If there are no option categories then the request is likely to be complete
488            # Display confirmation page if needed
489            if CONFIRMATION_PAGE==1 and not self.args.has_key("confirm"):
490                self.displayer._displayHTTPHeader()
491                self.displayer._displayHTMLHeader()
492                if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
493                self.displayer._displayDatasetSummaryLine(summaryString, optionCategories, self.sessionID)         
494                self.displayer._displayMainTableHeader(self.sessionID)   
495                print '<INPUT NAME="action" TYPE="hidden" VALUE="requestCosts">'
496                self.displayer._displayConfirmationSection(summaryDict)
497               
498            # Or display final job processing page     
499            elif self.args.has_key("action") and self.args["action"]=="requestCosts":   
500                response=self.server.callServerMethod("getExtractionCosts",
501                                                          [self.sessionID, self.secureToken])  #[0][0:2]
502                if zsiv>=2.0:
503                    response=response[0]
504
505                (estimatedDuration, estimatedVolume)=response[0:2]
506                self.displayer._displayHTTPHeader()
507                self.displayer._displayHTMLHeader()
508                if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
509                self.displayer._displayProcessingSection(estimatedDuration, estimatedVolume, self.sessionID)
510                response=self.server.callServerMethod("createOutput", [self.sessionID, self.secureToken])  #[0]
511               
512                if zsiv>=2.0:
513                    response=response[0]
514                pathList, self.secureToken=response
515                #print "<P>", pathList, len(pathList)
516                self.displayer._displayOutputFileList(pathList)
517
518
519    def _packArgsAsList(self, args):
520        """
521        In order to work with ZSI SOAP library need to pack up arguments as a list
522        of [keyword, value] pairs rather than a dictionary.
523        """
524        newList=[]
525        for key, value in args.items():
526            newList.append([key, value])
527        return newList
528       
529
530if __name__=="__main__":
531
532    DXCGIClient()       
Note: See TracBrowser for help on using the repository browser.