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

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

Updated version of MEDIN ingest - will ingest NEODC DIFs, convert to ISO then load ISO into DB - all original NDG3 functionality inc configurable ingest now included.

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
13from 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, stubIso = None):
38                 
39                 
40        logging.info("Setting up Postgres record for file, " + filename)
41       
42       
43        self.isoDataModel = isoDataModel
44       
45        self.filename = self.isoDataModel.isoFileLocation
46   
47       
48        #note method of extracting info from isoDataModel - nested lists, so if one value then use [0][0]
49        discovery_id = self.isoDataModel.datasetID[0][0]
50   
51        # NB, if we're dealing with an NDG data provider, the details are slightly different
52        if ndg_dataprovider:
53            discObj=ndgObject(discovery_id)
54            self._local_id = discObj.localID
55            self._repository_local_id = discObj.repository
56        else:
57            self._local_id = discovery_id
58            self._repository_local_id = datacentre_namespace
59           
60        self._datacentre_groups = datacentre_groups
61        self._repository = datacentre_namespace
62        self.discovery_id = discovery_id # just a single val..
63        self._xq = xq
64       
65        # simplify processing by uppercasing format at initialisation
66        self.docType = docType.upper()   
67               
68        #make sure we escape any special characters in this field... SJD 20/10/09       
69        #self.dataset_name = self.escapeSpecialCharacters(self.isoDataModel.datasetName[0])
70        self.dataset_name = self.escapeSpecialCharacters(self.isoDataModel.datasetName[0][0])
71       
72        self.datacentre_name = self.isoDataModel.datacentreName[0][0]
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        # initialise the various record fields
103        self.db_id = None    # the DB ID of the record, for easy reference when it is created
104        #self.molesFormat = None
105        self.dcFormat = None
106        #self.mdipFormat = None
107        self.iso19139Format = None
108        self.scn = 1    # system change number - keeps track of number of mods to a particular row
109       
110        # spatiotemporal data object
111        self.stData = None
112       
113        # fields to hold author, parameter and scope data
114        self.authors = None
115        self.parameters = None
116        self.scope = None
117       
118        #info needed for performing xquery conversions in the MEDIN ingest stack
119        self._xqueryConversionsExceptions = xqExceptions       
120        self._xqueryConversions = xqueryConversions       
121        self._xqueryDocTypes = xqueryDocTypes       
122        self._saxonJarFile = saxonJarFile
123       
124        self.stubISO = self.escapeSpecialCharacters(stubIso)
125        self.originalXMLdoc = self.escapeSpecialCharacters(originalXML)
126       
127       
128
129    def escapeSpecialCharacters(self, inputString):
130        '''
131        Adjust the input string to escape any characters that would interfere with string or DB
132        operations
133        @param inputString: string to correct
134        @return: corrected string
135        '''
136        return re.sub(r'\'', '\\\'', inputString)
137
138
139    def unescapeSpecialCharacters(self, inputString):
140        '''
141        Adjust the input string to remove escaped characters that would interfere with string or DB
142        operations
143        @param inputString: string to correct
144        @return: corrected string
145        '''
146        str = re.sub(r'%20', ' ', inputString)
147        return 
148   
149   
150    def doRecordTransforms(self):
151        '''
152        Run various transforms on the original doc, to populate the record with
153        the other types of doc used elsewhere
154        '''
155        logging.info("Running transforms for all document types")
156        for docType in self.documentTypes:
157            self.getDocumentFormat(docType)
158           
159        logging.info("Transforms complete")
160
161
162       
163
164    def doTransform(self, xQueryType):
165        '''
166        Transform the record according to the specified XQuery type
167        @param xQueryType: XQuery doc to use to do the transform
168        @return: the metadata record in the required transformed format
169        '''
170        logging.info("Running XQuery transform, " + xQueryType + " to create TRANSFORMED document!!")
171
172           
173        #takes metadataFileLoc,repositoryName,metadataID,metadataFilename, saxonJar
174        #self.xqueryTransformation = xqueryTransformation(self.discovery_dir,self._repository,self.discovery_id,self._local_id,self._saxonJarFile)
175        self.xqueryTransformation = xqueryTransformation(self.discovery_dir,self._repository,self._local_id,self.discovery_id,self._saxonJarFile)
176        self.transformedXML = self.xqueryTransformation.runXquery(xQueryType)
177                                       
178        return self.transformedXML
179
180
181
182
183    def getDocumentFormat(self, docType):
184        '''
185        Lookup document format; if it is already defined then return it, else do the required XQuery
186        transform.  NB, transforms are ran on the molesFormat document - so ensure this is available
187        @param docType: format of document to return
188        '''
189       
190       
191        logging.info("Retrieving document type, " + docType)
192       
193        # the doc type doesn't exist - so run the xquery
194        transformedDoc = self.doTransform(docType)
195       
196        #not sure if we still need to do this..?
197        setattr(self, docType, transformedDoc)
198       
199       
200        return transformedDoc
201       
202   
203    def getAllDocs(self,transformationDir):
204        '''
205        Return a list of all the available doc types in the record
206        '''
207        # if the stored docs array is the same size as the array of all doc types
208        # assume all transforms have been done - and just return these
209       
210        self.discovery_dir = transformationDir
211               
212        for docType in self._xqueryConversions:
213               
214                self._allDocs.append([self._xqueryDocTypes[docType], self.escapeSpecialCharacters(self.getDocumentFormat(docType))])
215               
216        #remember, if a non ISO input format we need to add the stubISO intermediate format too.
217        #(if original format was ISO, this is covered by the self.originalXMLdoc overrider in the insertMetadata method in PostgresDAO.)
218       
219        if self.stubISO is not None:
220                logging.info("Transient ISO intermediate format detected; adding to transformed docs!")
221                self._allDocs.append(['stubISO',self.stubISO])
222       
223       
224        return self._allDocs
225       
226   
227
228    def listify(self, item):
229        '''
230        listify checks if an item is a list, if it isn't it puts it
231        inside a list and returns it. Always returns a list object.
232        @param item: object to check
233        @return: item as a list object
234        '''
235        if type(item) is list:
236            return item
237        else:
238            return [item]
239       
240   
241    def getSpatioTemporalData(self):
242        '''
243        Extract spatio temporal data from the original document
244        '''
245        logging.info('Retrieving spatiotemporal info from ISO file')
246        # initialise the various spatiotemporal arrays used to extract data to
247       
248       
249        '''
250        self.stData = SpatioTemporalData()
251       
252        if self.dgMeta is None:
253            self.createMolesFile()
254           
255           
256        # do quick checks to see if the relevant data exists
257        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary:
258            logging.info("No data summary elements found - assuming no spatiotemporal data available")
259            return
260       
261        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage:
262            logging.info("No data coverage elements found - assuming no spatiotemporal data available")
263            return
264       
265        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage.dgSpatialCoverage:
266            logging.info("No spatial coverage elements found - assuming no spatial data available")
267        else:
268            self.getCoordData(self.dgMeta)
269
270        #SJD error with line below- this is where 23/09/08 edit in PostgresDAO fudge sorts...
271        if not self.dgMeta.dgMetadataRecord.dgDataEntity.dgDataSummary.dgDataCoverage.dgTemporalCoverage:
272            logging.info("No temporal coverage elements found - assuming no temporal data available")
273        else:
274            self.getTimeRangeData(self.dgMeta)
275                '''
276   
277    def getAuthorsInfo(self):
278        '''
279        Extract authors info from the iso object
280        '''
281       
282        logging.info("Extracting author info")
283       
284        #simple method to generate a space delimited list of authors from isoObject
285       
286        self.authors = ''
287       
288        #remember the double list return style..
289        if self.isoDataModel.authors[0][0] == 'None':
290                logging.info("No author information present")
291        else:
292                for author in self.isoDataModel.authors[0]:
293                        self.authors = self.authors + ' ' +author
294       
295        return self.authors
296       
297       
298   
299    def getParametersInfo(self):
300        '''
301        Extract parameters info from the moles file
302        '''
303        logging.info('\nRetrieving parameters info from moles file\n')
304       
305        self.params = ""
306       
307        #remember the double list return style..
308        if self.isoDataModel.parameters[0][0] == 'None':
309                logging.info("No parameter information present")
310        else:
311                for param in self.isoDataModel.parameters[0]:
312                        self.params = self.params + ' ' + param
313       
314        return self.params
315       
316   
317    def getScopeInfo(self):
318        '''
319        Extract scope info from keywords in the input file
320        '''
321        logging.info('Retrieving scope info from moles file')
322       
323        #TODO - put this in configuration file!
324        acceptedScopeVals = ['NERC_DDC','DDDP','MEDIN']
325       
326        scopeVals = self.isoDataModel.keywordsList
327       
328        scope = ""
329       
330        for keyword in scopeVals:
331                if keyword in acceptedScopeVals:
332                        scope += " " + keyword
333               
334        self.scope = re.sub(r'_', 'UNDERSCORE', scope)
335       
336        return self.scope
337     
338       
339    '''
340    Method to parse & check temporal coverage information extracted from the original ISO object
341    '''
342    def parseTemporalInfo(self,timeData):
343       
344        logging.info("Parsing Temporal information from original ISO object")
345       
346        TimeRange = []
347        if len(timeData) == 0:
348                        logging.info("No temporal coverage elements found - assuming no temporal data available")
349               
350        else:
351                for time in timeData:
352                        start = time['start']
353                        end = time['end']
354               
355                        if start is None:
356                           startDate = "null"
357                        else:
358                           startDate = start
359                       
360                        if end is None:
361                           endDate = "null"
362                        else:
363                           endDate = end
364                       
365                        if (end is None) and (start is None):
366                           logging.info("No temporal coverage elements found - assuming no temporal data available")
367                        else:
368                                TimeRange.append({'start':startDate,'end':endDate})
369                                                       
370                       
371                        logging.info("Time range data found; start: " + startDate + " end: " + endDate)
372                               
373                return TimeRange
374                               
375   
376    '''
377    Method to parse & check Spatial coverage information extracted from the original ISO object
378    '''
379    def parseSpatialInfo(self,spatialData):
380       
381        logging.info("Parsing Spatial information from original ISO object")
382       
383        SpatialCoords = []
384        if len(spatialData) == 0:
385                        logging.info("No spatial coverage elements found - assuming no spatial data available")
386                       
387        else:
388                cntr = 1
389                for coords in spatialData:
390                                if (coords['north'] is None) or (coords['north'] == 'None'):
391                                        north = "null"
392                                else:
393                                    north = coords['north']
394                                   
395                                if (coords['south'] is None) or (coords['south'] == 'None'):
396                                        south = "null"
397                                else:
398                                    south = coords['south']
399                                   
400                                if (coords['east'] is None) or (coords['east'] == 'None'):
401                                    east = "null"
402                                   
403                                else:
404                                    east = coords['east']
405                                   
406                                if (coords['west'] is None) or (coords['west'] == 'None'):
407                                    west = "null"
408                                   
409                                else:
410                                    west = coords['west']
411                                   
412                                #check the coordinates - NOTE now using MEDIN based coords these need to be ISO defined lon/lats in decimal degrees?
413                                #if not JUST WARN - DIF's might have UKNG coords..
414                                if (north > 90) or (north < -90) or (south > 90) or (south < -90):
415                                        logging.warn("*****************************************************************")
416                                        logging.warn("*** WARNING: latitude coordinates outside of accepted bounds! ***")
417                                        logging.warn("*****************************************************************")
418                                       
419                                if (west > 180) or (west < -180) or (east > 180) or (east < -180):
420                                        logging.warn("*****************************************************************")
421                                        logging.warn("*** WARNING: longitude coordinates outside of accepted bounds! ***")
422                                        logging.warn("*****************************************************************")
423                                       
424                                   
425                                SpatialCoords.append({'west':west,'east':east,'north':north,'south':south})
426                               
427                                logging.info("( " + str(cntr) + ") Spatial Coords found....")
428                                logging.info("................ east: " + east)
429                                logging.info("................ west: " + west)
430                                logging.info("................ north: " + north)
431                                logging.info("................ south: " + south)
432                                cntr += 1
433                               
434        return SpatialCoords
435               
436                       
437       
438       
439       
440       
Note: See TracBrowser for help on using the repository browser.