source: TI01-discovery-Ingest/trunk/v4n_MEDIN/ingestAutomation-upgrade/OAIBatch/PostgresRecord.py @ 6846

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery-Ingest/trunk/v4n_MEDIN/ingestAutomation-upgrade/OAIBatch/PostgresRecord.py@6846
Revision 6846, 18.3 KB checked in by sdonegan, 10 years ago (diff)

Put in some code to deal with multiple definitions of datacentre name depending on expath chosen

Line 
1#!/usr/bin/env python
2'''
3Class representing the a document to be ingested into the postgres DB table
4C Byrom Apr 08
5'''
6from xml.etree import cElementTree
7import os, sys, logging, re, pkg_resources
8#import csml.csml2Moles.molesReadWrite as MRW
9from ndg.common.src.models.ndgObject import ndgObject
10from ndg.common.src.lib.ndgresources import ndgResources
11import ndg.common.src.lib.fileutilities as FileUtilities
12from Utilities import xqueryTransformation
13#from SpatioTemporalData import SpatioTemporalData
14import keywordAdder
15
16SAXON_JAR_FILE = 'lib/saxon9.jar'
17
18class PostgresRecord:
19    '''
20    Class representing the a document to be ingested into the postgres DB table
21    @param filename: Name of file to use a metadata record
22    @param ndg_dataprovider
23    @param datacentre_groups
24    @param datacentre_namespace
25    @param discovery_id
26    @param xq
27    @param doctype - type of doc to process
28    '''
29    # TODO MDIP transforms do not work very well for lots of files - so currently hiding these
30    #documentTypes = ['DIF', 'DC', 'ISO19139']#, 'MDIP']
31   
32    # vocab server - used for finding scope values in the moles files
33    ndg_data_provider_vocab = "http://vocab.ndg.nerc.ac.uk/term/N010"
34       
35    #def __init__(self, filename, ndg_dataprovider, datacentre_groups, datacentre_namespace, discovery_id, xq, docType):
36    def __init__(self, filename, ndg_dataprovider, datacentre_groups, datacentre_namespace, isoDataModel, xq, docType
37                                , xqExceptions , xqueryConversions, saxonJarFile, xqueryDocTypes, originalXML, currentMedinStandard, stubIso = None):
38                 
39                 
40        logging.info("Setting up Postgres record for file, " + filename)             
41       
42        self.isoDataModel = isoDataModel
43       
44        self.filename = self.isoDataModel.isoFileLocation
45   
46       
47        #note method of extracting info from isoDataModel - nested lists, so if one value then use [0][0]
48        discovery_id = self.isoDataModel.datasetID[0][0]
49   
50        # NB, if we're dealing with an NDG data provider, the details are slightly different
51        if ndg_dataprovider:
52            discObj=ndgObject(discovery_id)
53            self._local_id = discObj.localID
54            self._repository_local_id = discObj.repository
55        else:
56            self._local_id = discovery_id
57            self._repository_local_id = datacentre_namespace
58           
59        self._datacentre_groups = datacentre_groups
60        self._repository = datacentre_namespace
61        self.discovery_id = discovery_id # just a single val..
62        self._xq = xq
63       
64        # simplify processing by uppercasing format at initialisation
65        self.docType = docType.upper()   
66       
67        #make sure we escape any special characters in this field... SJD 20/10/09       
68        #self.dataset_name = self.escapeSpecialCharacters(self.isoDataModel.datasetName[0])
69       
70        self.dataset_name = self.escapeSpecialCharacters(self.isoDataModel.datasetName[0][0])
71               
72        self.datacentre_name = self.getSingleVal(self.isoDataModel.datacentreName)
73       
74        #self.dataset_lastEdit = datasetLastEditUpdateDate
75        self.dataset_lastEdit = self.isoDataModel.revisionDate[0][0]
76           
77        #self.datasetStartNom = datasetStartDateNom
78        self.datasetStartNom = self.isoDataModel.boundingDatesRange['start'] #dictionary method!
79       
80        #self.datasetEndNom = datasetEndDateNom
81        self.datasetEndNom = self.isoDataModel.boundingDatesRange['end'] #dictionary method!   
82       
83        self.datasetTemporalData = self.isoDataModel.boundingDates # set whole list of dictionaries for this as may be multiple boxes
84       
85        self.datasetSpatialData = self.isoDataModel.boundingBoxCoordinates # set whole list of dictionaries for this as may be multiple boxes
86       
87        self._molesFormat = None    # initialise this, so we can guarantee a value - to avoid using getattr
88        self._allDocs = []  # array to store all the transformed docs - for easy retrieval by the DAO
89
90        # get the dir of the file - needed by the xquery to use as the target collection
91        tmp = filename.split('/')
92        self._dir = '/'.join(tmp[0:len(tmp)-1])
93        self.shortFilename = tmp[-1]
94       
95       
96        #self.originalFormat = self.isoDataModel.originalFormat[0][0]
97        self.originalFormat = file(self.filename).read()
98       
99        # escape any apostrophes
100        self.originalFormat = self.escapeSpecialCharacters(self.originalFormat)
101       
102        self.currentMedinStandard = currentMedinStandard
103               
104        # initialise the various record fields
105        self.db_id = None    # the DB ID of the record, for easy reference when it is created
106        #self.molesFormat = None
107        self.dcFormat = None
108        #self.mdipFormat = None
109        self.iso19139Format = None
110        self.scn = 1    # system change number - keeps track of number of mods to a particular row
111       
112        # spatiotemporal data object
113        self.stData = None
114       
115        # fields to hold author, parameter and scope data
116        self.authors = None
117        self.parameters = None
118        self.scope = None
119       
120        #info needed for performing xquery conversions in the MEDIN ingest stack
121        self._xqueryConversionsExceptions = xqExceptions       
122        self._xqueryConversions = xqueryConversions       
123        self._xqueryDocTypes = xqueryDocTypes       
124        self._saxonJarFile = saxonJarFile
125       
126       
127        self.originalXMLdoc = self.escapeSpecialCharacters(originalXML)
128       
129        #need t define stubISO only if ingesting a dif.
130        if self.originalXMLdoc == "null":
131             self.stubISO = None
132        else:
133                self.stubISO = self.escapeSpecialCharacters(stubIso)
134               
135               
136    def getSingleVal(self, inputList):
137       
138                '''
139                Simple method to sort through a value list from the ISOdata model and check if any values are present, if not return an empty string
140                This gets around any list based errors when fishing for mandatory values
141               
142                @param inputList: list of values corresponding to xpath expressions used to fill the inputList from the original xml
143                @return: string: empty if no list values present, otherwise the first value if many
144                '''
145               
146                valList = []
147                #if type(item) is list:
148                #loop through all vals present and add to a temp list
149                for value in inputList:
150                        if len(value) > 0:                             
151                                        valList = valList + value
152               
153                #take the first val.. if there is one (gotta put some cardinality in somewhere!)
154                if len(valList) > 0:
155                        return valList[0]
156                else:
157                        #if not, return an empty string
158                        return ''
159       
160   
161    def escapeSpecialCharacters(self, inputString):
162        '''
163        Adjust the input string to escape any characters that would interfere with string or DB
164        operations
165        @param inputString: string to correct
166        @return: corrected string
167        '''
168        return re.sub(r'\'', '\\\'', inputString)
169
170
171    def unescapeSpecialCharacters(self, inputString):
172        '''
173        Adjust the input string to remove escaped characters that would interfere with string or DB
174        operations
175        @param inputString: string to correct
176        @return: corrected string
177        '''
178        str = re.sub(r'%20', ' ', inputString)
179        return 
180   
181   
182    def doRecordTransforms(self):
183        '''
184        Run various transforms on the original doc, to populate the record with
185        the other types of doc used elsewhere
186        '''
187        logging.info("Running transforms for all document types")
188        for docType in self.documentTypes:
189            self.getDocumentFormat(docType)
190           
191        logging.info("Transforms complete")
192
193
194       
195
196    def doTransform(self, xQueryType):
197        '''
198        Transform the record according to the specified XQuery type
199        @param xQueryType: XQuery doc to use to do the transform
200        @return: the metadata record in the required transformed format
201        '''
202        logging.info("Running XQuery transform, " + xQueryType + " to create TRANSFORMED document!!")
203       
204       
205        #takes metadataFileLoc,repositoryName,metadataID,metadataFilename, saxonJar
206        #self.xqueryTransformation = xqueryTransformation(self.discovery_dir,self._repository,self.discovery_id,self._local_id,self._saxonJarFile)
207        self.xqueryTransformation = xqueryTransformation(self.discovery_dir,self._repository,self._local_id,self.discovery_id,self._saxonJarFile)
208        self.transformedXML = self.xqueryTransformation.runXquery(xQueryType)
209                                       
210        return self.transformedXML
211
212
213
214
215    def getDocumentFormat(self, docType):
216        '''
217        Lookup document format; if it is already defined then return it, else do the required XQuery
218        transform.  NB, transforms are ran on the molesFormat document - so ensure this is available
219        @param docType: format of document to return
220        '''
221       
222       
223        logging.info("Retrieving document type, " + docType)
224       
225        # the doc type doesn't exist - so run the xquery
226        transformedDoc = self.doTransform(docType)
227       
228        #not sure if we still need to do this..?
229        setattr(self, docType, transformedDoc)
230       
231       
232        return transformedDoc
233       
234   
235    def getAllDocs(self,transformationDir):
236        '''
237        Return a list of all the available doc types in the record
238        '''
239        # if the stored docs array is the same size as the array of all doc types
240        # assume all transforms have been done - and just return these
241       
242        self.discovery_dir = transformationDir
243       
244               
245        for docType in self._xqueryConversions:
246               
247                self._allDocs.append([self._xqueryDocTypes[docType], self.escapeSpecialCharacters(self.getDocumentFormat(docType))])
248               
249        #remember, if a non ISO input format we need to add the stubISO intermediate format too.
250        #(if original format was ISO, this is covered by the self.originalXMLdoc overrider in the insertMetadata method in PostgresDAO.)
251       
252        if self.docType == 'DIF_9.4':
253                logging.info("Transient ISO intermediate format detected; adding to transformed docs!")
254               
255                #we must cast this stubISO as the MEDIN format ISO so can be selected ("provide MEDIN format output from non-MEDIN input")
256                self._allDocs.append([self.currentMedinStandard,self.stubISO])
257               
258        else:
259               
260                #if not DIF, put original format ISO in here
261                #TODO - put in version with redirected URLs??
262                self._allDocs.append([self.docType,self.originalFormat])
263       
264        return self._allDocs
265       
266   
267
268    def listify(self, item):
269        '''
270        listify checks if an item is a list, if it isn't it puts it
271        inside a list and returns it. Always returns a list object.
272        @param item: object to check
273        @return: item as a list object
274        '''
275        if type(item) is list:
276            return item
277        else:
278            return [item]
279       
280   
281    def getSpatioTemporalData(self):
282        '''
283        Extract spatio temporal data from the original document
284        '''
285        logging.info('Retrieving spatiotemporal info from ISO file')
286        # initialise the various spatiotemporal arrays used to extract data to
287       
288       
289        '''
290        self.stData = SpatioTemporalData()
291       
292        if self.dgMeta is None:
293            self.createMolesFile()
294           
295           
296        # do quick checks to see if the relevant data exists
297        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary:
298            logging.info("No data summary elements found - assuming no spatiotemporal data available")
299            return
300       
301        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage:
302            logging.info("No data coverage elements found - assuming no spatiotemporal data available")
303            return
304       
305        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage.dgSpatialCoverage:
306            logging.info("No spatial coverage elements found - assuming no spatial data available")
307        else:
308            self.getCoordData(self.dgMeta)
309
310        #SJD error with line below- this is where 23/09/08 edit in PostgresDAO fudge sorts...
311        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage.dgTemporalCoverage:
312            logging.info("No temporal coverage elements found - assuming no temporal data available")
313        else:
314            self.getTimeRangeData(self.dgMeta)
315                '''
316   
317    def getAuthorsInfo(self):
318        '''
319        Extract authors info from the iso object
320        '''
321       
322        logging.info("Extracting author info")
323       
324        #simple method to generate a space delimited list of authors from isoObject       
325        self.authors = ''
326       
327        #remember the double list return style..
328        if len(self.isoDataModel.authors[0])==0:
329                logging.info("No author information present (none not caught but extractISO.py - sort it!")
330                self.authors = "null"
331                return self.authors
332               
333        if self.isoDataModel.authors[0][0] == 'None':           
334                logging.info("No author information present")
335                self.authors = "null"
336        else:
337                for author in self.isoDataModel.authors[0]:
338                        self.authors = self.authors + ' ' +author
339       
340        return self.authors
341       
342       
343   
344    def getParametersInfo(self):
345        '''
346        Extract parameters info from the moles file
347        '''
348        logging.info('\nRetrieving parameters info from moles file\n')
349       
350        self.params = ""
351       
352        #remember the double list return style..
353        if self.isoDataModel.parameters[0][0] == 'None':
354                logging.info("No parameter information present")
355        else:
356                for param in self.isoDataModel.parameters[0]:
357                        self.params = self.params + ' ' + param
358       
359        return self.params
360       
361   
362    def getScopeInfo(self):
363        '''
364        Extract scope info from keywords in the input file
365        '''
366        logging.info('Retrieving scope info from moles file')
367       
368        #TODO - put this in configuration file!
369        acceptedScopeVals = ['NERC_DDC','DDDP','MEDIN','NDGO0001']
370       
371        scopeVals = self.isoDataModel.keywordsList
372       
373        scope = ""
374       
375        for keyword in scopeVals:
376                if keyword in acceptedScopeVals:
377                       
378                        #deal with MEDIN using NDGO0001 as MEDIN scope val
379                        if keyword == 'NDGO0001':
380                                keyword = 'MEDIN'
381                               
382                        scope += " " + keyword
383               
384        self.scope = re.sub(r'_', 'UNDERSCORE', scope)
385       
386        return self.scope
387     
388       
389    '''
390    Method to parse & check temporal coverage information extracted from the original ISO object
391    '''
392    def parseTemporalInfo(self,timeData):
393       
394        logging.info("Parsing Temporal information from original ISO object")
395       
396        TimeRange = []
397        if len(timeData) == 0:
398                        logging.info("No temporal coverage elements found - assuming no temporal data available")
399               
400        else:
401                for time in timeData:
402                        start = time['start']
403                        end = time['end']
404               
405                        if start is None:
406                           startDate = "null"
407                        else:
408                           startDate = start
409                       
410                        if end is None:
411                           endDate = "null"
412                        else:
413                           endDate = end
414                       
415                        if (end is None) and (start is None):
416                           logging.info("No temporal coverage elements found - assuming no temporal data available")
417                        else:
418                                TimeRange.append({'start':startDate,'end':endDate})
419                                                       
420                       
421                        logging.info("Time range data found; start: " + startDate + " end: " + endDate)
422                               
423                return TimeRange
424                               
425   
426    '''
427    Method to parse & check Spatial coverage information extracted from the original ISO object
428    '''
429    def parseSpatialInfo(self,spatialData):
430       
431        logging.info("Parsing Spatial information from original ISO object")
432       
433        SpatialCoords = []
434        if len(spatialData) == 0:
435                        logging.info("No spatial coverage elements found - assuming no spatial data available")
436                       
437        else:
438                cntr = 1
439                for coords in spatialData:
440                                if (coords['north'] is None) or (coords['north'] == 'None'):
441                                        north = "null"
442                                else:
443                                    north = coords['north']
444                                   
445                                if (coords['south'] is None) or (coords['south'] == 'None'):
446                                        south = "null"
447                                else:
448                                    south = coords['south']
449                                   
450                                if (coords['east'] is None) or (coords['east'] == 'None'):
451                                    east = "null"
452                                   
453                                else:
454                                    east = coords['east']
455                                   
456                                if (coords['west'] is None) or (coords['west'] == 'None'):
457                                    west = "null"
458                                   
459                                else:
460                                    west = coords['west']
461                                   
462                                #check the coordinates - NOTE now using MEDIN based coords these need to be ISO defined lon/lats in decimal degrees?
463                                #if not JUST WARN - DIF's might have UKNG coords..
464                                if (north > 90) or (north < -90) or (south > 90) or (south < -90):
465                                        logging.warn("*****************************************************************")
466                                        logging.warn("*** WARNING: latitude coordinates outside of accepted bounds! ***")
467                                        logging.warn("*****************************************************************")
468                                       
469                                if (west > 180) or (west < -180) or (east > 180) or (east < -180):
470                                        logging.warn("*****************************************************************")
471                                        logging.warn("*** WARNING: longitude coordinates outside of accepted bounds! ***")
472                                        logging.warn("*****************************************************************")
473                                       
474                                   
475                                SpatialCoords.append({'west':west,'east':east,'north':north,'south':south})
476                               
477                                logging.info("( " + str(cntr) + ") Spatial Coords found....")
478                                logging.info("................ east: " + east)
479                                logging.info("................ west: " + west)
480                                logging.info("................ north: " + north)
481                                logging.info("................ south: " + south)
482                                cntr += 1
483                               
484        return SpatialCoords
485               
486                       
487       
488       
489       
490       
Note: See TracBrowser for help on using the repository browser.