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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery/branches/ingestion-MEDIN/ingestAutomation-upgrade/OAIBatch/Utilities.py@6046
Revision 6046, 16.0 KB checked in by sdonegan, 10 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.error_messages = ""
312               
313                logging.info("Have Initiated Ingest monitor!")
314               
315        def incrementProblemFile(self):
316                self._no_problem_files += 1
317               
318        def incrementIngestFile(self):
319                self._no_files_ingested += 1
320       
321        def incrementChangeFile(self):
322                self._no_files_changed += 1
323       
324        def appendFailList(self,filename):
325                self.updateFailList.append(filename)
326
327        def appendDeletedList(self,filename):
328                self.deletedFailList.append(filename)
329
330        def addToErrorMessage(self,message):
331                self.error_messages = self.error_message + message
332               
333'''
334Class representing a record to be deleted from the postgres DB.  Mimics the class "PostgresRecord" as passed to PostgresDAO
335This is so we can use the PostgresDAO class for DB manipulation
336
337Merged into Utilities June 2009 SJD
338'''   
339class RecordToDelete:
340   
341    '''
342    Class representing a document to be deleted from the postgres DB
343    @param filename: Name of the original_document_file   
344    '''
345   
346    def __init__(self, filename, discovery_ID):
347       
348       
349        #this relates to the "original_document_filename" column in the original_document table
350        if filename is not None:
351            self.filename = filename
352        else:
353            self.filename = "temporaryFilename"
354           
355        if discovery_ID is not None:
356            self.discovery_id = discovery_ID
357        else:
358            self.discovery_id = "temporaryDiscoveryID"
359       
360        # initialise the various record fields
361        self.db_id = None    # the DB ID of the record, for easy reference when it is created
362       
363
364
365class DatasetBasicParameters_Original:
366   
367    '''
368    Class representing extracted parameters from the input file
369    @param filename: Name of the original_document_file   
370    '''
371   
372    def __init__(self, filename,format):
373       
374        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
375        xml=file(filename).read()
376               
377        self._datacentre_format = format
378       
379        et=loadET(xml)
380        helper=nsdumb(et)
381        if ((self._datacentre_format=='DIF') or (self._datacentre_format=='dif')):
382            #return helper.getText(et,'Entry_ID')
383            self.datasetID=helper.getText(et,'Entry_ID')
384            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
385            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
386            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
387            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
388           
389            #need to make sure that latest date, eother from creation or last revision is present.
390            if helper.getText(et,'Last_DIF_Revision_Date') != '':
391                self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')               
392            else:               
393                self.metadataCreationDate=helper.getText(et,'DIF_Creation_Date')
394               
395            #Fudge to get around some DC's using "entry_title" and others "dataset_title".  grrr.
396            if self.datasetName == '':
397             self.datasetName == helper.getText(et,'Entry_Title')
398               
399            #TODO amend this - just a fudge to ingest records from crappy badc/neodc whilst pipeline down;..
400            if self.datasetEndDateNom == '':
401             self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/End_Date')                       
402           
403        elif self._datacentre_format == 'MDIP':
404            #return helper.getText(self.tree,'DatasetIdentifier')
405            self.datasetID=helper.getText(et,'DatasetIdentifier')
406            self.datasetName = helper.getText(et,'DatasetIdentifier')
407            self.datacentreName = helper.getText(et,'Distributor/DistributorName/DistributorNameName')
408            self.metadataCreationDate=helper.getText(et,'DateOfUpdateOfMetadata')
409            self.datasetStartDateNom = helper.getText(et,'Date/DatasetStartDate')
410            self.datasetEndDateNom = helper.getText(et,'Date/DatasetEndDate')
411        else:
412            raise TypeError,'idget does not support datatype [%s]'%dataType
413           
414        #if no values for start or end dates need to set these to NULL!
415        if self.datasetStartDateNom == "":
416            self.datasetStartDateNom = 'NULL'
417               
418        if self.datasetEndDateNom == "":
419            self.datasetEndDateNom = 'NULL'
420           
421
422class DatasetBasicParameters_MEDIN_v01:
423   
424    '''
425    Class representing extracted parameters from the input file - MEDIN format v0.1 (development)
426    @param filename: Name of the original_document_file   
427    '''
428   
429    def __init__(self, filename,format):
430       
431        logging.info("Retrieving identifier for metadata record " + filename + " in format: " + format)
432        xml=file(filename).read()
433               
434        self._datacentre_format = format
435       
436        et=loadET(xml)
437        helper=nsdumb(et)
438       
439        try:
440
441            self.datasetID=helper.getText(et,'Entry_ID')
442            self.datasetName = helper.getText(et,'Data_Set_Citation/Dataset_Title')
443            self.datacentreName = helper.getText(et,'Data_Center/Data_Center_Name/Short_Name')           
444            self.datasetStartDateNom = helper.getText(et,'Temporal_Coverage/Start_Date')
445            self.datasetEndDateNom = helper.getText(et,'Temporal_Coverage/Stop_Date')
446            self.metadataCreationDate=helper.getText(et,'Last_DIF_Revision_Date')
447            self.datasetName == helper.getText(et,'Entry_Title')
448            self.datasetEndDateNom == helper.getText(et,'Temporal_Coverage/End_Date')
449               
450        except:
451                raise TypeError,'idget does not support datatype [%s]'%dataType
452       
453       
454           
455        #if no values for start or end dates need to set these to NULL!
456        if self.datasetStartDateNom == "":
457            self.datasetStartDateNom = 'NULL'
458               
459        if self.datasetEndDateNom == "":
460            self.datasetEndDateNom = 'NULL'
461
462
463
464import unittest
465
466class TestCase(unittest.TestCase):
467    """ Tests as required """
468
469    def testidget(self):
470        self.assertEqual(idget(self.difxml),'NOCSDAT192')
471   
472
473if __name__=="__main__":
474    unittest.main()
475
476
477
Note: See TracBrowser for help on using the repository browser.