source: TI01-discovery/branches/ingestion-MEDIN/ingestAutomation-upgrade/OAIBatch/Utilities.py @ 6048

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery/branches/ingestion-MEDIN/ingestAutomation-upgrade/OAIBatch/Utilities.py@6312
Revision 6048, 16.6 KB checked in by sdonegan, 11 years ago (diff)

Updated with new object for tacking ingest vars

Line 
1from xml.etree import ElementTree as ET
2import logging,pkg_resources
3from ndg.common.src.lib.ETxmlView import loadET, nsdumb
4from ndg.common.src.models.ndgObject import ndgObject
5from ndg.common.src.lib.ndgresources import ndgResources
6import ndg.common.src.lib.fileutilities as FileUtilities
7
8def idget(xml,dataType='DIF'):
9    ''' Given an xml document (string), parse it using ElementTree and
10    find the identifier within it. Supports dataTypes of 'DIF' and 'MDIP'...
11    '''
12    et=loadET(xml)
13    helper=nsdumb(et)
14    if dataType=='DIF':
15        return helper.getText(et,'Entry_ID')
16    elif dataType == 'MDIP':
17        return helper.getText(self.tree,'DatasetIdentifier')
18    else:
19        raise TypeError,'idget does not support datatype [%s]'%dataType
20 
21import os, sys, logging
22
23'''
24        Class to handle identityTransform and correct UTF handling of parsable URLS for use in the ndg redirect service
25
26        Currently handles either DIF or MDIP input format
27
28        Steve Donegan June 2009 STFC RAL
29'''
30
31from xml.etree import ElementTree as ET
32import logging,urllib
33
34class redirectUrlChanger:
35
36       
37
38        ''' Reads XML vocab structure. '''
39        def __init__(self,filenameIP,outputFilenameIP,formatIP,redirectURLIP):
40               
41                self.format = formatIP
42                self.filename = filenameIP
43                self.outputFilename = outputFilenameIP
44                self.redirectBaseURL = redirectURLIP
45                               
46                #parse the xml with elementtree
47                self.etree=ET.parse(self.filename)
48                self.root=self.etree.getroot() # should be the "vocab" element
49
50                #look for and change relevant portions - fudge it here for now
51                self.difContentType = ["GET SERVICE"]
52       
53       
54                if self.format == 'DIF':
55                        self.changeUrl_dif()
56               
57                elif self.format == 'MDIP':
58                        self.changeUrl_mdip()
59           
60                #rewrite this back into the xml doc     
61                self.etree.write(self.outputFilename)
62
63
64        '''
65        Method to obtain and change selected URLs - for MEDIN vN input
66        '''
67        def changeUrl_medin_vN(self, contentType):
68
69                url = ''
70
71                return url
72
73               
74        '''
75        Method to obtain and change selected URLs - for MDIP input
76        '''
77        def changeUrl_mdip(self):
78
79                try:
80                        docTitle = self.root.find(self.appendNameSpace(self.datasetTitle())[0]).text
81                except:
82                        #doesnt matter too much if this doesnt work!
83                        docTitle = 'NOT_AVAILABLE'
84                       
85                try:
86                       
87                        docID = self.root.find(self.appendNameSpace(self.getIdPath())[0]).text
88                       
89                        self.paths = self.appendNameSpace(self.URLpaths_MDIP())
90                                               
91                       
92                        for path in self.paths:
93                                                                                                       
94                                nodes = self.root.findall(path) ##finds all related_url/url
95                               
96                                for node in nodes:
97                                       
98                                        currentURL = urllib.quote(node.txt)
99                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
100                                        node.text = redirectURL
101                       
102                except:
103                        print "Cannot perform identityTransform to rewrite ndg redirect urls on MDIP input format doc"
104                        logging.warn("Cannot perform identityTransform to rewrite ndg redirect urls on MDIP format doc")
105
106
107               
108        '''
109        Method to obtain and change selected URLs - for GCDM DIF input
110        '''
111        def changeUrl_dif(self):
112               
113                #get document id
114                #note as appendNameSPace returns a list, must define which element, only one entry_id so 0 here... (java is so much better at this).
115               
116                problem=False
117               
118                try:
119                        docTitle = self.root.find(self.appendNameSpace(self.datasetTitle())[0]).text
120                except:
121                        #doesnt matter too much if this doesnt work!
122                        docTitle = 'NOT_AVAILABLE'
123                        problem = True
124                               
125                try:
126                        docID = self.root.find(self.appendNameSpace(self.getIdPath())[0]).text
127                except:
128                        docID = 'NOT_AVAILABLE'
129                        problem = True
130               
131                #get available DIF paths
132                self.paths = self.appendNameSpace(self.URLpaths_DIF())
133               
134                for path in self.paths:
135                               
136                        try:
137                       
138                                nodes = self.root.findall(path) ##finds all related_url/url
139                       
140                                for node in nodes:
141                                                               
142                                        #have to handle different paths available for urls in difs differently - related_url is a complex type, and is
143                                        #the one we have to change depending on url_content_type so must do this differently to elsewhere
144                                        if path == self.appendNameSpace(['Related_URL'])[0]:
145                                                                                               
146                                                difChildElements = node.getiterator()
147                                                                                                                                               
148                                                #build dictionary of node name content pairs                                   
149                                                children={}
150                                                for child in difChildElements:
151                                                        children[child.tag] = child.text
152                                                       
153                                                #check for URL_Content_Type - these are the keys of the elements we want
154                                                content = self.appendNameSpace(['URL_Content_Type'])[0]
155                                                url = self.appendNameSpace(['URL'])[0] 
156                                                                                                                                                                                       
157                                                #go over the keys - if url_content_type is in there..           
158                                                if content in children.keys():                                                 
159                                                        urlContentType = children[content]
160                                                                                                               
161                                                        #using the values in contentType, check to see if present
162                                                        for ct in self.difContentType:
163                                                               
164                                                                               
165                                                                #..if a match then we actually WANT the original URL
166                                                                if ct not in urlContentType:                                                                           
167                                                                                                                                               
168                                                                        #if no match, work out redirect url
169                                                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
170                                                                       
171                                                                        #if this related_url element is to be changed, access the element directly as a node
172                                                                        urlNode = node.find(self.appendNameSpace(['URL'])[0])
173                                               
174                                                                        #now set the url to the redirected value                                                       
175                                                                        urlNode.text = redirectURL                                                     
176                                                                       
177                                                #if no url_content_type element present, continue to rewrite the url
178                                                else:
179                                                        urlNode = node.find(self.appendNameSpace(['URL'])[0])
180                                                        currentURL = urllib.quote(urlNode.text)
181                                                        redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
182                                                        urlNode.text = redirectURL
183                                                       
184                                                                                                                                                                               
185                                        #other paths of non related_url complex type
186                                        else:                                           
187                                                currentURL = urllib.quote(node.text)
188                                                redirectURL = self.redirectBaseURL + children[url] + '&docID=' + urllib.quote(docID) + '&docTitle=' + urllib.quote(docTitle)
189                                                node.text = redirectURL
190                       
191                        except:
192                                print "Cannot perform identityTransform to rewrite ndg redirect urls!"
193                                logging.warn("Cannot perform identityTransform to rewrite ndg redirect urls!")
194
195               
196                        #cannot rewrite doc so just leave doc as original..
197       
198       
199        '''
200        Method to convert the given xpath list to a namespace abbreviated version so elementtree can handle it (grrr.)
201        '''
202        def appendNameSpace(self,paths):
203               
204                nameSpaceAppendedPaths = []
205
206                for path in paths:                     
207                        pathElements = path.split('/')
208                       
209                        #note due to crappy elementtree have to avoid initial "/"
210                        count = 0
211                        for element in pathElements:
212                                if count == 0:
213                                        appendedPath = self.returnNS() + element
214                                else:
215                                        appendedPath = appendedPath + '/' + self.returnNS() + element
216                               
217                                count += 1
218                                                       
219                        nameSpaceAppendedPaths.append(appendedPath)
220
221                return nameSpaceAppendedPaths
222
223       
224        '''method to handle xpath to title for acceptable formats'''
225        def datasetTitle(self):
226                if self.format == 'DIF':
227                        return ['Entry_Title']
228                elif self.format == 'MDIP':
229                        return ['Title']
230
231        '''method to handle xpath for id if for reqd format '''
232        def getIdPath(self):
233                if self.format == 'DIF':
234                        return ['Entry_ID']
235                elif self.format == 'MDIP':
236                        return ['DatasetIdentifier']
237       
238        '''method to handle xpath for expected URLS if for reqd format '''
239        def URLpaths_DIF(self):
240                return ['Related_URL','Data_Center/Data_Center_URL']
241               
242        def URLpaths_MDIP(self):               
243                return ['OnlineResource','Distributor/Web']
244
245        '''method to handle default namespaces for reqd format '''
246        def returnNS(self):
247                if self.format == 'DIF':
248                        return '{http://gcmd.gsfc.nasa.gov/Aboutus/xml/dif/}' #Note that ns has to be encapsulated in {}'s!
249                elif self.format == 'MDIP':
250                        return '{http://www.oceannet.org/mdip/xml}'
251
252'''
253Class operating the identity transform of new ingest docs to change all urls to include a redirect via the ndg redirect service
254so can record all traffic from discovery service elsewhere.  Rewrites back to original filename so can continue with ingest
255'''
256class ndgRedirectTransform:
257
258    def __init__(self, xQueryType,ndgRedirect,dir):
259
260           
261        SAXON_JAR_FILE = 'lib/saxon9.jar'
262
263       
264        _repository_local_id = 'notneeded'
265        _local_id = 'notneeded'
266
267        xqueryLib = ndgResources()       
268        xquery = xqueryLib.createXQuery(xQueryType,dir, _repository_local_id, _local_id)
269        xquery = xquery.replace('INPUT_FILE',dir)
270        xquery = xquery.replace('NDG_REDIRECT_URL',ndgRedirect)
271
272        #generate the actual xqueryFile
273        xqFile = "redirectQuery_"+xQueryType+".xq"
274        FileUtilities.createFile(xqFile, xquery)
275       
276
277        # ensure the jar file is available - NB, this may be running from a different
278        # location - e.g. the OAIInfoEditor.lib.harvester - and this won't have the
279        # saxon file directly on its filesystem
280        jarFile = pkg_resources.resource_filename('OAIBatch','lib/saxon9.jar')
281
282        # Now do the transform
283        os.putenv ('PATH', ':/usr/java/jdk1.5.0_03/bin:/usr/java/jdk1.5.0_03:/usr/java/jdk1.5.0_03/lib/tools.jar:/usr/local/WSClients/OAIBatch:/usr/local/exist-client/bin:/bin:/usr/bin:.')
284        xqCommand = "java -cp %s net.sf.saxon.Query %s !omit-xml-declaration=yes" %(jarFile, xqFile)
285        logging.debug("Running saxon command: " + xqCommand)
286        pipe = os.popen(xqCommand + " 2>&1")
287        output = pipe.read()
288        status = pipe.close()
289                       
290        if status is not None:
291            raise SystemError, 'Failed at running the XQuery'
292        else:
293            #write the output to a file for the rest of the ingest chain
294            FileUtilities.createFile(dir, output)
295       
296            logging.info("Have performed identityTransform on " + dir)
297
298
299'''
300Class to hold ingest tracking values - easier to pass as object
301'''
302class IngestTracking:
303                       
304        def __init__(self):
305               
306                self.updateFailList = []
307                self.deletedFailList = []
308                self._no_problem_files = 0
309                self._no_files_ingested = 0                     
310                self._no_files_changed = 0
311                self._no_deleted_files = 0
312                self.error_messages = ""
313               
314                logging.info("Have Initiated Ingest monitor!")
315               
316        def incrementProblemFile(self):
317                self._no_problem_files += 1
318               
319        def getProblemFileNum(self):
320                return self._no_problem_files
321               
322        def incrementIngestFile(self):
323                self._no_files_ingested += 1
324               
325        def getIngestedFileNum(self):
326                return self._no_files_ingested
327       
328        def incrementChangeFile(self):
329                self._no_files_changed += 1
330               
331        def getChangedFileNum(self):
332                return self.incrementChangeFile()
333       
334        def inrementDeletedFile(self):
335                self._no_deleted_files += 1
336               
337        def getDeletedFileNum(self):
338                return self._no_deleted_files
339       
340        def appendFailList(self,filename):
341                self.updateFailList.append(filename)
342               
343        def getFailList(self):
344                return self.updateFailList
345
346        def appendDeletedList(self,filename):
347                self.deletedFailList.append(filename)
348               
349        def getDeletedList(self):
350                return self.deletedFailList
351
352        def addToErrorMessage(self,message):
353                self.error_messages = self.error_message + message
354               
355        def getErrorMessage(self):
356                return self.error_messages
357               
358        def testMsg(self,text):
359                return "Thankyou for sending me this text: " + text + "('tis just a test of cr@ppy python OO)"
360               
361'''
362Class representing a record to be deleted from the postgres DB.  Mimics the class "PostgresRecord" as passed to PostgresDAO
363This is so we can use the PostgresDAO class for DB manipulation
364
365Merged into Utilities June 2009 SJD
366'''   
367class RecordToDelete:
368   
369    '''
370    Class representing a document to be deleted from the postgres DB
371    @param filename: Name of the original_document_file   
372    '''
373   
374    def __init__(self, filename, discovery_ID):
375       
376       
377        #this relates to the "original_document_filename" column in the original_document table
378        if filename is not None:
379            self.filename = filename
380        else:
381            self.filename = "temporaryFilename"
382           
383        if discovery_ID is not None:
384            self.discovery_id = discovery_ID
385        else:
386            self.discovery_id = "temporaryDiscoveryID"
387       
388        # initialise the various record fields
389        self.db_id = None    # the DB ID of the record, for easy reference when it is created
390       
391
392
393class DatasetBasicParameters_Original:
394   
395    '''
396    Class representing extracted parameters from the input file
397    @param filename: Name of the original_document_file   
398    '''
399   
400    def __init__(self, filename,format):
401       
402        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
403        xml=file(filename).read()
404               
405        self._datacentre_format = format
406       
407        et=loadET(xml)
408        helper=nsdumb(et)
409        if ((self._datacentre_format=='DIF') or (self._datacentre_format=='dif')):
410            #return helper.getText(et,'Entry_ID')
411            self.datasetID=helper.getText(et,'Entry_ID')
412            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
413            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
414            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
415            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
416           
417            #need to make sure that latest date, eother from creation or last revision is present.
418            if helper.getText(et,'Last_DIF_Revision_Date') != '':
419                self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')               
420            else:               
421                self.metadataCreationDate=helper.getText(et,'DIF_Creation_Date')
422               
423            #Fudge to get around some DC's using "entry_title" and others "dataset_title".  grrr.
424            if self.datasetName == '':
425             self.datasetName == helper.getText(et,'Entry_Title')
426               
427            #TODO amend this - just a fudge to ingest records from crappy badc/neodc whilst pipeline down;..
428            if self.datasetEndDateNom == '':
429             self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/End_Date')                       
430           
431        elif self._datacentre_format == 'MDIP':
432            #return helper.getText(self.tree,'DatasetIdentifier')
433            self.datasetID=helper.getText(et,'DatasetIdentifier')
434            self.datasetName = helper.getText(et,'DatasetIdentifier')
435            self.datacentreName = helper.getText(et,'Distributor/DistributorName/DistributorNameName')
436            self.metadataCreationDate=helper.getText(et,'DateOfUpdateOfMetadata')
437            self.datasetStartDateNom = helper.getText(et,'Date/DatasetStartDate')
438            self.datasetEndDateNom = helper.getText(et,'Date/DatasetEndDate')
439        else:
440            raise TypeError,'idget does not support datatype [%s]'%dataType
441           
442        #if no values for start or end dates need to set these to NULL!
443        if self.datasetStartDateNom == "":
444            self.datasetStartDateNom = 'NULL'
445               
446        if self.datasetEndDateNom == "":
447            self.datasetEndDateNom = 'NULL'
448           
449
450class DatasetBasicParameters_MEDIN_v01:
451   
452    '''
453    Class representing extracted parameters from the input file - MEDIN format v0.1 (development)
454    @param filename: Name of the original_document_file   
455    '''
456   
457    def __init__(self, filename,format):
458       
459        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
460        xml=file(filename).read()
461               
462        self._datacentre_format = format
463       
464        et=loadET(xml)
465        helper=nsdumb(et)
466       
467        try:
468
469            self.datasetID=helper.getText(et,'Entry_ID')
470            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
471            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
472            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
473            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
474            self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')
475            self.datasetName == helper.getText(et,'Entry_Title')
476            self.datasetEndDateNom == helper.getText(et,'Temporal_Coverage/End_Date')
477               
478        except:
479                raise TypeError,'idget does not support datatype [%s]'%dataType
480       
481       
482           
483        #if no values for start or end dates need to set these to NULL!
484        if self.datasetStartDateNom == "":
485            self.datasetStartDateNom = 'NULL'
486               
487        if self.datasetEndDateNom == "":
488            self.datasetEndDateNom = 'NULL'
489
490
491
492import unittest
493
494class TestCase(unittest.TestCase):
495    """ Tests as required """
496
497    def testidget(self):
498        self.assertEqual(idget(self.difxml),'NOCSDAT192')
499   
500
501if __name__=="__main__":
502    unittest.main()
503
504
505
Note: See TracBrowser for help on using the repository browser.