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

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

Unstable but latest version with multi-variable support and split hooks
for CDML and CSML.

  • 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
18
19# Import SOAP library
20from ZSI.client import Binding
21       
22# Import package modules
23from pydxc import *
24
25# Set up CGI error reporting if DEBUG is set to 1 in clientConfig.py module
26if DEBUG==1:
27    import cgitb
28    cgitb.enable()
29
30
31class DXCGIClient:
32    """
33    The controlling class for interacting with the dx via the web.
34    """
35   
36    def __init__(self):
37        """
38        Initiates the instance setting up the appropriate
39        internal objects and calls to the relevant classes depending
40        on the configuration and arguments provided.
41        """
42        self.username=None
43        self.password=None
44        self.secureToken=None
45        self.userRoles=[]
46        self.loginStatus="out"
47        self.loginMessage=""
48        self.sessionObject={}
49       
50        self.displayer=DisplayManager()
51
52        # Parse the arguments
53        try:
54            self._parseArgs()
55        except:
56            if DEBUG==1:
57                raise
58            else:
59                CGIErrorHandler("Error parsing arguments: "+str(sys.exc_type), noheader=0)
60
61        # Destroy any arguments not intended to pass any further
62        try:
63            self._destroyUnnecessaryArgs()
64        except:
65            if DEBUG==1:
66                raise
67            else:
68                CGIErrorHandler("Error destroying arguments: "+str(sys.exc_type), noheader=0)
69
70        # If switched on check security       
71        if RESTRICTED_DATA==1:
72            try:
73                self._checkSecurity()
74            except:
75                if DEBUG==1:
76                    raise
77                else:
78                    CGIErrorHandler("Security error: "+str(sys.exc_type), noheader=0)
79        else:
80            self.secureToken=None             
81           
82        # Call the dx Web Service with the arguments provided       
83        try:
84            self._callDXWebService(self.args)   
85        except SystemExit:
86            pass         
87        except:
88            if DEBUG==1:
89                raise
90            else:
91                CGIErrorHandler("Error calling dx Web Service: "+str(sys.exc_type), noheader=0)       
92
93        try:   
94            self.displayer._displayHTMLFooter()
95        except:
96            if DEBUG==1:
97                raise
98            else:
99                CGIErrorHandler("Error generating HTML footer: "+str(sys.exc_type), noheader=0)
100       
101       
102    def _parseArgs(self):
103        """
104        Parses the arguments sent, if any, ready for sending to the
105        web service.
106        """
107        # Get arguments
108        self.args={}                           
109        args=cgi.FieldStorage()
110       
111        # Get upload file link
112        if args.has_key("uploadedFile"):
113            self.tempFileLink=args["uploadedFile"].file
114            self.args["fileName"]=args["uploadedFile"].filename
115
116        # Populate the argument dictionary
117        for key in args.keys():
118            if key=="uploadedFile": continue
119            self.args[key]=args.getvalue(key)
120                   
121        if self.args.has_key("sessionID") and self.args["sessionID"]=="None":
122            self.args["sessionID"]=None     
123         
124        # Check for secure items, destroy if necessary and assign as instance variables   
125        if self.args.has_key("yousirnaim"):
126            self.username=self.args["yousirnaim"]
127            self.args["username"]=self.username
128            del self.args["yousirnaim"]
129           
130        if self.args.has_key("parcewerd"):
131            self.password=self.args["parcewerd"]
132            del self.args["parcewerd"] 
133               
134        if self.args.has_key("secureToken"):
135            self.secureToken=self.args["secureToken"]
136            del self.args["secureToken"]   
137       
138        if not self.args.has_key("username"):
139            self.args["username"]=None
140           
141        self._determineNumberOfDatasets()
142        self._parsePartialDimensionArgs()
143
144
145    def _determineNumberOfDatasets(self):
146        """
147        Returns the number of datasets the user is requesting.
148        This affects how the output is produced.
149        """
150        ndsetPattern=re.compile(r"^\w+_(\d+)$")
151        self.numberOfDatasets=1  # Dummy value for now
152       
153        for key in self.args.keys():
154            match=ndsetPattern.match(key)
155            if match:
156                dsetNumber=int(match.groups()[0])
157                if dsetNumber>self.numberOfDatasets:
158                    self.numberOfDatasets=dsetNumber
159
160        if self.args.has_key("numberOfDatasets"):
161            try:
162                self.numberOfDatasets=int(self.args["numberOfDatasets"])
163            except:
164                self.numberOfDatasets=int(self.args.getvalue("numberOfDatasets"))       
165
166
167    def _parsePartialDimensionArgs(self):
168        """
169        Parses dimension arguments so that they are of the
170        correct types to send to the server.
171        """                 
172        # Make a list of all the possible time keys to parse in time args
173        allTimeKeys=createTimeKeyList()
174                       
175        timeKeyCount=0   
176        for key in self.args.keys():
177            if key in allTimeKeys:
178                # Make sure it is an integer
179                try:
180                    self.args[key]=int(self.args[key])
181                    timeKeyCount=timeKeyCount+1
182                except:
183                    pass                   
184            elif key in HORIZ_KEYS:
185                try:
186                    self.args[key]=float(self.args[key])
187                except:
188                    pass
189               
190        if timeKeyCount>0:
191            self._compileDateTimeArgs()             
192                   
193
194    def _compileDateTimeArgs(self):
195        """
196        Converts a list of arguments for each date and time component
197        to a start and end date/time following the xsd:dateTime description.
198        """
199        dateTimeString="%.4d-%.2d-%.2dT%.2d:%.2d:%.2d"
200        # Build item lists of year, month, day...second for each dataset
201        # both start and end date times.
202        # Also delete the original component items for clarity.
203        for n in range(1, self.numberOfDatasets+1):
204            startList=[]
205            endList=[]
206            for tk in TIME_KEYS:
207                startList.append(self.args["start%s_%s" % (tk, n)])
208                del self.args["start%s_%s" % (tk, n)]
209                endList.append(self.args["end%s_%s" % (tk, n)])
210                del self.args["end%s_%s" % (tk, n)]             
211           
212            self.args["startDateTime_%s" % n]=dateTimeString % tuple(startList)
213            self.args["endDateTime_%s" % n]=dateTimeString % tuple(endList)
214           
215
216    def _destroyUnnecessaryArgs(self):
217        """
218        Destroy the arguments that should not be passed to the main dx Web Service.
219        """
220        if self.args.has_key("password"):
221            del self.args["password"]   
222       
223       
224    def _checkSecurity(self):
225        """
226        If security is switched on with RESTRICTED_DATA=1 then this will
227        call the local implementation of the security.
228        """
229        secChecker=SecurityViaCGI(self.username, self.password, self.secureToken)
230       
231        # Deal with logout
232        ############# NOTE - doesn't destroy session server side (yet)
233        if self.args.has_key("logout") and self.args["logout"]=="Logout":
234            secChecker.logout()
235            secCheck="You have been logged out."
236        else:
237            secCheck=secChecker.validate()         
238           
239        if type(secCheck)==type(""):
240            # Returned string means error in log in or logged out
241            self.loginMessage=secCheck
242        else:
243            self.loginStatus="in"
244            self.loginMessage=""
245            (self.secureToken, self.username, self.userRoles)=secCheck   
246
247               
248    def _callDXWebService(self, args):
249        """
250        According to the arguments given this binds to an appropriate
251        Web Service and calls it with the relevant arguments.
252        """
253        # Just print the login page if not logged in and login required
254        if RESTRICTED_DATA==1 and self.loginStatus=="out":
255            self.displayer._displayHTTPHeader()
256            self.displayer._displayHTMLHeader()
257            self.displayer._displayIntroduction()           
258            self.displayer._displayLoginBar(self.username, self.loginStatus, self.loginMessage)
259            return     
260             
261        # Set up SOAP bindings 
262        if CALL_METHOD.upper()=="WS":
263            self.server=WSCaller()
264        elif CALL_METHOD.upper()=="LOCAL":
265            serverLocation=LOCAL_SERVER_PACKAGE
266            sys.path.append(serverLocation)
267            print sys.path
268            self.server=LocalCaller()
269
270        # Get session ID
271        self._getSession()
272       
273        # Check if complete
274        isCompleteStatus=self._checkIfComplete()
275       
276        # If an argument received as "numberOfDatasets" then call WS to set that
277       
278        if self.args.has_key("numberOfDatasets"):
279            status, self.secureToken=self.server.callServerMethod("setNumberOfDatasets",
280                                          [self.sessionID, self.args["numberOfDatasets"],
281                                           self.secureToken])
282       
283        # Perform actions as requested by user
284        if self.args.has_key("action"):
285            actionReturn=self._performActions()
286            if actionReturn=="Display footer then complete":
287                return
288       
289        # Clear request if required
290        if self.args.has_key("clearRequest") or self.args.has_key("newRequest"):
291            if self.args.has_key("clearRequest"):
292                del self.args["clearRequest"]
293            else:
294                del self.args["newRequest"]
295            #response=self._webServiceCallWrapper(self.server.newRequest(self.sessionID, self.secureToken))
296            #self.sessionID, self.secureToken=response     
297            self.sessionID, self.secureToken=self.server.callServerMethod("newRequest",
298                                                  [self.sessionID, self.secureToken])                                           
299
300        # Make sure instance and args sessionID are the same
301        self.args["sessionID"]=self.sessionID
302       
303        # Ensure args contains secureToken object
304        if not self.args.has_key("secureToken"):
305            self.args["secureToken"]=self.secureToken
306
307        # Get the options (and make selections if appropriate)
308        (optionCategories, options, optionStrings, secureToken)=self._getLatestOptions()
309
310        # ***Can probably delete ***Update secure token after
311        #if RESTRICTED_DATA==1:
312        #    self._upDateSecureToken(secureToken)       
313       
314        # Get summary of request and determine number of datasets   
315        #response=self._webServiceCallWrapper(self.server.summariseRequest(self.sessionID, self.secureToken))
316        #self.summary=response[0]
317        self.summary=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
318        numDatasetsMatch=re.search(r"numberOfDatasets:\t(\d+)", self.summary)
319        if numDatasetsMatch:
320            self.numberOfDatasets=int(numDatasetsMatch.groups()[0])
321           
322        # Analyse the request
323        self._respondToOptionCategories(optionCategories, options, optionStrings)       
324   
325       
326    def _getSession(self):
327        """
328        Checks if a session is already underway and starts one if not.
329        """           
330        # If no session then start a session and get a sessionID
331        if not self.args.has_key("sessionID") or self.args["sessionID"] in (None, "None"):
332            # Start session if not known about
333            #response=self._webServiceCallWrapper(self.server.startSession(self.username, self.password, self.secureToken))
334            #self.sessionID, self.secureToken=response
335            #self.sessionID=self._deUnicodeObject(self.sessionID)
336            self.sessionID, self.secureToken=self.server.callServerMethod("startSession",
337                                                 [self.username, self.password, self.secureToken])
338        else:
339            self.sessionID=self.args["sessionID"]
340           
341           
342    def _checkIfComplete(self):
343        """
344        Checks if complete, returns 1 (yes) or 0 (no).
345        """
346        isCompleteStatus=0       
347        if self.args.has_key("isComplete"):
348            #response=self._webServiceCallWrapper(self.server.isComplete(self.sessionID, self.secureToken))
349            #isCompleteStatus, self.secureToken=response       
350            isCompleteStatus, self.secureToken=self.server.callServerMethod("isComplete",
351                                                      [self.sessionID, self.secureToken])     
352        return isCompleteStatus
353       
354       
355    def _performActions(self):
356        """
357        If "action" argument received then do the appropriate action.
358        """     
359        if 1:  # Move all back <-- one tab and delete this line
360            action=self.args["action"]
361            if action=="viewRequestSummary":
362                #response=self._webServiceCallWrapper(self.server.summariseRequest(self.sessionID, self.secureToken))       
363                #summary=response[0]
364                summary=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
365                self.displayer._displayHTTPHeader()
366                self.displayer._displayHTMLHeader()
367                self.displayer._displayRequestSummaryTable(summary)
368                self.displayer._displayReturnLine(self.sessionID)
369                return "Display footer then complete"
370               
371            elif action=="saveRequest":
372                #response=self._webServiceCallWrapper(self.server.getDataSubsetSpecifier(self.sessionID, self.secureToken))
373                #dataSubsetSpecifier, self.secureToken=response
374                dataSubsetSpecifier, self.secureToken=self.server.callServerMethod("getDataSubsetSpecifier",
375                                                           [self.sessionID, self.secureToken])
376                self.displayer._displayHTTPHeader()
377                self.displayer._displayHTMLHeader()             
378                self.displayer._displaySaveRequestOptions(dataSubsetSpecifier, self.sessionID)
379                self.displayer._displayReturnLine(self.sessionID)
380                return "Display footer then complete"
381               
382            elif action=="uploadRequest":
383                self.displayer._displayHTTPHeader()
384                self.displayer._displayHTMLHeader()
385                self.displayer._displayUploadRequestOptions(self.sessionID)
386                self.displayer._displayReturnLine(self.sessionID)               
387                return "Display footer then complete"           
388               
389            elif action=="parseUploadedFile":
390                xmlFileString=self.tempFileLink.read()
391                #del self.args["uploadedFile"]
392                #response=self._webServiceCallWrapper(self.server.uploadRequest(self.sessionID, xmlFileString, self.secureToken))
393                #status, self.secureToken=response         
394                status, self.secureToken=self.server.callServerMethod("uploadRequest",
395                                              [self.sessionID, xmlFileString, self.secureToken])
396       
397         
398    def _getLatestOptions(self):
399        """
400        If target page provided then get the options for that page,
401        otherwise get whatever options are presented by the dx and
402        send latest selections to update request.
403        """
404        if self.args.has_key("targetPage"):
405            tp=self.args["targetPage"]
406            tp2catMap={"DatasetGroupPage":"datasetGroup", "DatasetPage":"dataset",
407                       "VariablesPage":"variable", "DomainPage":"horizontalDomain"}
408            optionCategory=tp2catMap[tp]
409            #response=self._webServiceCallWrapper(self.server.getOptions(self.sessionID, self.secureToken, optionCategory))
410            #(optionCategories, options, optionStrings, secureToken)=response
411            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("getOptions",
412                                                                         [self.sessionID, self.secureToken,
413                                                                         optionCategory])
414        else: 
415            #print "Content-Type: text/html\n\n"
416            #args=self._packArgsAsList(self.args)
417            #print str(args)
418            #r=self.server.selectOptions(self.sessionID, [])
419            #raise str(r)
420            #r=apply(self.server.selectOptions, [self.sessionID, args])
421            #response=self._webServiceCallWrapper(apply(self.server.selectOptions, [self.sessionID, args]))
422            #response=self._deUnicodeObject(response)                       
423            #(optionCategories, options, optionStrings, secureToken)=response
424            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
425                                                                       [self.sessionID, self._packArgsAsList(self.args)])
426       
427        return (optionCategories, options, optionStrings, secureToken)
428
429
430    def _respondToOptionCategories(self, optionCategories, options, optionStrings):
431        """
432        Work out what the option category is and respond by displaying
433        the appropriate user interface.
434        """     
435        # Analyse the option categories now
436        if optionCategories=="No category":
437            optcat=None
438        else:
439            optcat=optionCategories[0].split("_")[0]
440       
441        # Can display simple form for these categories 
442        if optcat in ("datasetGroup", "dataset", "variable"):           
443            self.displayer._displayHTTPHeader()
444            self.displayer._displayHTMLHeader()     
445            if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
446            self.displayer._displayDatasetSummaryLine(self.summary, optionCategories[0], self.sessionID)
447            self.displayer._displayMainTableHeader(self.sessionID)
448            self.displayer._displayOptionsTable(optionCategories, options, optionStrings, self.sessionID, self.sessionObject)
449            self.displayer._displayMainTableFooter()
450       
451        # Need to make a number of web service calls to get information
452        # for each domain as well as output format which are all
453        # displayed on the same page.   
454        elif optcat=="horizontalDomain":
455            self.displayer._displayHTTPHeader()
456            self.displayer._displayHTMLHeader()
457            self.displayer._displayDatasetSummaryLine(self.summary, optionCategories[0], self.sessionID)
458            self.displayer._displayMainTableHeader(self.sessionID)         
459            print "<P><B>SPATIAL AND TEMPORAL SELECTION</B><P>"     
460            self.displayer._displayHorizontalDomainOptions(options)
461           
462            # Now get the vertical domain options
463            self.args["optionCategoryRequested"]="verticalDomain"
464            #response=self._webServiceCallWrapper(apply(self.server.selectOptions, [self.sessionID, self._packArgsAsList(self.args)]))
465            #response=self._deUnicodeObject(response)       
466            #(optionCategories, options, optionStrings, secureToken)=response   
467            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
468                                                                         [self.sessionID, self._packArgsAsList(self.args)])
469            self.displayer._displayVerticalSpatialDomainOptions(options)
470
471            # Now get the temporal domain options
472            self.args["optionCategoryRequested"]="temporalDomain"           
473            #response=self._webServiceCallWrapper(apply(self.server.selectOptions, [self.sessionID, self._packArgsAsList(self.args)]))
474            #response=self._deUnicodeObject(response)               
475            #(optionCategories, options, optionStrings, secureToken)=response   
476            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
477                                                                         [self.sessionID, self._packArgsAsList(self.args)])
478            self.displayer._displayTemporalDomainOptions(options)
479
480            # Now get the output format options
481            self.args["optionCategoryRequested"]="outputFormat"     
482            #response=self._webServiceCallWrapper(apply(self.server.selectOptions, [self.sessionID, self._packArgsAsList(self.args)]))
483            #response=self._deUnicodeObject(response)               
484            #(optionCategories, options, optionStrings, secureToken)=response       
485            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
486                                                                         [self.sessionID, self._packArgsAsList(self.args)])         
487            self.displayer._displayOutputFormatOptions(options, self.sessionID)   
488       
489        # If there are no option categories then the request is likely to be complete
490        elif optcat==None:
491       
492            # Display confirmation page if needed
493            if CONFIRMATION_PAGE==1 and not self.args.has_key("confirm"):
494                self.displayer._displayHTTPHeader()
495                self.displayer._displayHTMLHeader()
496                self.displayer._displayDatasetSummaryLine(self.summary, optionCategories[0], self.sessionID)       
497                self.displayer._displayMainTableHeader(self.sessionID)   
498                print '<INPUT NAME="action" TYPE="hidden" VALUE="requestCosts">'
499                self.displayer._displayConfirmationSection(self.summary)
500               
501            # Or display final job processing page     
502            elif self.args.has_key("action") and self.args["action"]=="requestCosts":
503                #response=self._webServiceCallWrapper(self.server.getExtractionCosts(self.sessionID, self.secureToken))
504                #response=self._deUnicodeObject(response)
505                #(estimatedDuration, estimatedVolume)=response[0:2]                 
506                (estimatedDuration, estimatedVolume)=self.server.callServerMethod("getExtractionCosts",
507                                                          [self.sessionID, self.secureToken])[0:2]
508                self.displayer._displayHTTPHeader()
509                self.displayer._displayHTMLHeader()
510                self.displayer._displayProcessingSection(estimatedDuration, estimatedVolume, self.sessionID)       
511                #response=self._webServiceCallWrapper(self.server.createOutput(self.sessionID, self.secureToken))
512                #response=self._deUnicodeObject(response)               
513                #pathList, self.secureToken=response
514                pathList, self.secureToken=self.server.callServerMethod("createOutput", [self.sessionID, self.secureToken])             
515                self.displayer._displayOutputFileList(pathList[0])
516                               
517
518    def _webServiceCallWrapper(self, response):
519        """
520        Analyses the response from a Web Service call to check if an error has
521        occurred. If so it parses and reports the error. Otherwise it returns 
522        the object returned from the Web Service call.
523        """
524        if type(response)==type(""):
525            # String returns are errors
526            self.displayer._displayErrorPage(response)
527            self.displayer._displayHTMLFooter()
528            sys.exit()
529        else:
530            return response                     
531
532
533    def _packArgsAsList(self, args):
534        """
535        In order to work with ZSI SOAP library need to pack up arguments as a list
536        of [keyword, value] pairs rather than a dictionary.
537        """
538        newList=[]
539        for key, value in args.items():
540            newList.append([key, value])
541        return newList
542       
543
544    def _deUnicodeObject(self, obj):
545        """
546        Returns an identical object with all unicode strings returned as normal strings.
547        """
548        return deUnicodeObject(obj)
549        """if type(obj)==type(u""):
550            return str(obj)
551        elif type(obj)==type([]):
552            newList=[]
553            for item in obj:
554                if type(item)==type(u""):
555                    newList.append(str(item))
556                elif type(item)==type([]):
557                    extraList=[]
558                    for i in item:
559                        if type(i)==type(u""):
560                            extraList.append(str(i))
561                        elif type(i)==type([]):
562                            anotherList=[]
563                            for a in i:
564                                if type(a)==type(u""):
565                                    anotherList.append(str(a))
566                                elif type(a)==type([]):
567                                    wowList=[]
568                                    for x in a:
569                                        if type(x)==type(u""):
570                                            wowList.append(str(x))
571                                        else:
572                                            wowList.append(x)
573                                    anotherList.append(wowList[:])
574                                else:
575                                    anotherList.append(a)
576                            extraList.append(anotherList[:])
577                        else:
578                            extraList.append(i)
579                    newList.append(extraList[:])
580                else:
581                    newList.append(item)
582            return newList
583        else:
584            return obj"""
585
586       
587    def _validateRequest(self):
588        """
589        Calls the validation Web Service to check that the current selection is
590        within the available options. This will be needed as users can write their
591        own XML files and upload them.
592        """
593        pass
594       
595
596if __name__=="__main__":
597
598    DXCGIClient()       
Note: See TracBrowser for help on using the repository browser.