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

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

Added validation checking for dataOriginator and ability to handle multiple originators

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