source: TI01-discovery-Ingest/trunk/v4.3.0/ingestAutomation-upgrade/OAIBatch/PostgresRecord.py @ 7286

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

Updates for NERC v4.3.0 MEDIN style ingest with new arg for process id for use with dpws with logging to database

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