source: mauRepo/MolesManager/trunk/cedaMoles/MolesManager/moles3epb.py @ 8496

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/mauRepo/MolesManager/trunk/cedaMoles/MolesManager/moles3epb.py@8496
Revision 8496, 19.6 KB checked in by mnagni, 7 years ago (diff)

Incomplete - # 22534: Add versiojn number to the gui page
 http://team.ceda.ac.uk/trac/ceda/ticket/22534

Line 
1'''
2BSD Licence
3Copyright (c) 2012, Science & Technology Facilities Council (STFC)
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without modification,
7are permitted provided that the following conditions are met:
8
9    * Redistributions of source code must retain the above copyright notice,
10        this list of conditions and the following disclaimer.
11    * Redistributions in binary form must reproduce the above copyright notice,
12        this list of conditions and the following disclaimer in the documentation
13        and/or other materials provided with the distribution.
14    * Neither the name of the Science & Technology Facilities Council (STFC)
15        nor the names of its contributors may be used to endorse or promote
16        products derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
22BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29Created on 10 Jan 2012
30
31@author: mnagni
32'''
33from cedaMoles.libs.epb import EPB
34from ea_model.moles3_4.observationcollection.mo_observationcollection import MO_ObservationCollection
35from ea_model.moles3_4.observation.mo_observation import MO_Observation
36from sqlalchemy import Table, Column, ForeignKey, Integer, String, event
37from sqlalchemy.orm import mapper
38from cedaMoles.MolesManager.ceda_guid import CedaGUID
39from sqlalchemy.orm.collections import InstrumentedList
40from ea_model.iso_19115_2006_metadata_corrigendum.reference_system_information.md_identifier import MD_Identifier
41from ea_model.iso_19115_2006_metadata_corrigendum.citation_and_responsible_party_information.ci_citation import CI_Citation
42from ea_model.iso_19115_2006_metadata_corrigendum.extent_information.ex_geographicboundingbox import EX_GeographicBoundingBox
43from cedaMoles.libs.postgisutil import create_st_setSRID, unifyGeometriesAsBBox
44from ea_model.ceda_metadatamodel.ceda_observationcollection.ceda_observationcollection import CEDA_ObservationCollection
45from ea_model.ceda_metadatamodel.ceda_observation.ceda_observation import CEDA_Observation
46from ea_model.ceda_metadatamodel.ceda_project.ceda_project import CEDA_Project
47from cedaMoles.MolesManager.codelist import MM_RoleValue, getCLValue
48from cedaMoles.libs.migration.processor.commons import from_pt_to_string
49from datetime import datetime
50from ascore.utils import synchAttributes
51from cedaMoles.MolesManager.db.partyIndexes import associateMOParty_indexes
52from cedaMoles.MolesManager.db.after_flush import afterFlush
53
54class Moles3EPBFactory(EPB):   
55    def __init__(self, db_manager):
56        super(Moles3EPBFactory, self).__init__(db_manager)
57        self._init_ceda_customization()
58
59    def _init_ceda_customization(self):
60        self._associate_ceda_guid()
61        associateMOParty_indexes(self._db_manager.metadata)
62        self._initSearchIndexes() 
63       
64    def _associate_ceda_guid(self):
65        guid_table = Table('ceda_guid', self._db_manager.metadata, \
66                           Column('id', String, primary_key=True), \
67                           Column('ceda_observationcollection', Integer, ForeignKey('ceda_observationcollection.id')), 
68                           Column('ceda_observation', Integer, ForeignKey('ceda_observation.id')),
69                           Column('ceda_project', Integer, ForeignKey('ceda_project.id')))
70        mapper(CedaGUID, guid_table)
71        self._db_manager.metadata.create_all()
72
73    def _initSearchIndexes(self):
74        #To Be Done - CHECK IF THE COLUMN ALREADY EXISTS!
75        # We don't want sqlalchemy to know about this column so we add it externally.
76        try:
77            self._db_manager.engine.execute("alter table md_identifier add column code_search_vector tsvector")                 
78
79            # This indexes the tsvector column
80
81            self._db_manager.engine.execute("create index md_identifier_code_search_index on md_identifier using gin(code_search_vector)")
82
83            # This sets up the trigger that keeps the tsvector column up to date.
84            self._db_manager.engine.execute("create trigger md_identifier_code_search_update before update or insert on md_identifier \
85                for each row execute procedure tsvector_update_trigger('code_search_vector', 'pg_catalog.english', code)")                       
86        except Exception:
87            pass
88       
89    def createEPB(self):
90        session = self._get_session()
91        event.listen(session, 'after_flush', afterFlush)
92        return Moles3EPB(session)
93
94class Moles3EPB(object):
95
96    def __init__(self, session):
97        self._session = session
98       
99    def close(self):
100        return self._session.close()       
101       
102    def searchEager(self, clazz, inst_id):
103        return EPB.searchEager(clazz, inst_id, self._session)     
104     
105    def _controlledCommit(self):
106        try:
107            self._session.commit()
108        except Exception as e:
109            print e
110
111    def rollback(self):
112        """
113        Rolls back the session and one a new transaction
114        """
115        EPB.rollback(self._session) 
116     
117    def persistInstance(self, instance):
118        """
119        Adds a new migration object.
120        **Parameters**
121            * instance: the object to add
122           
123        **Returns**
124            An updated, session independent, object instance reflecting the new persisted object
125        """
126        EPB.persistInstance(instance, self._session)       
127
128    def mergeInstance(self, instance):
129        """
130        Copy the state an instance onto the persistent instance with the same identifier.
131        **Parameters**
132            * instance: the object to add
133           
134        **Returns**
135            An updated object instance reflecting the new persisted object
136        """
137        return EPB.mergeInstance(instance, self._session) 
138
139    def refresh(self, instance):
140        """
141        Expires and refresh the attributes on the given instance.
142        **Parameters**
143            * instance: the object to refresh
144        """
145        EPB.refresh(instance, self._session) 
146
147    def deleteInstance(self, instance):
148        """
149        Deletes a new migration object.
150        **Parameters**
151            * instance: the object to delete
152        """
153        EPB.deleteInstance(instance, self._session) 
154
155    def expunge(self, instance):
156        """
157        Expunges an object from the session
158        **Parameters**
159            * instance: the object to expunge
160        """
161        EPB.expunge(instance, self._session) 
162
163    def _mergeOrAddInSession(self, ceda_object):
164        try:
165            return self._session.merge(ceda_object)   
166        except:
167            self._session.add(ceda_object)
168            return ceda_object 
169     
170    def updateCedaObject(self, ceda_object, cols_to_update):
171        """
172            Update, eventually add to the session, and commit a CEDA Object in MOLES3 db.
173            @param ceda_object: the CEDA object to update
174            @param cols_to_update: a dictionary containing the columns to update for the given ceda_object and the desired value.
175            If the attribute is a list of objects the new instances are appended only if do not exist in the actual list
176            @return: the given instance with the updated attributes.
177        """
178        coll = self._mergeOrAddInSession(ceda_object)
179        if coll != None:       
180            for k,v in cols_to_update.items():
181                if v is None:
182                    continue
183                if hasattr(coll, k):                   
184                    coll_k = getattr(coll, k)                       
185                    if type(coll_k) == list or type(coll_k) == InstrumentedList:
186                        tmp_coll = []
187                        if type(v) == list or type(v) == InstrumentedList:
188                            tmp_coll.extend(v)
189                        else:
190                            tmp_coll.append(v)
191                        for item in tmp_coll:
192                            el = self._mergeOrAddInSession(item)
193                            if el not in coll_k:
194                                coll_k.append(el)
195                    else:
196                        el = self._mergeOrAddInSession(v)
197                        setattr(coll, k, el)
198        synchAttributes(coll)                                             
199        self._controlledCommit()
200        #return coll                                     
201
202    def getUnifyObservationCollectionGEAsBBox(self, collection):
203        """
204            Returns the union of the collections.member'a  GeographicExtension(s)
205            @param collection: an CEDA_ObservationColleciton instance 
206        """
207        bboxes = []
208        for member in collection.member:
209            for ge in member.geographicExtent:
210                bboxes.append(getGeograpicExtentGeometry(ge))
211       
212        return unifyGeometriesAsBBox(bboxes, self) 
213       
214        #return unifyGeometriesAsBBox(bboxes, self)
215
216
217    def retrieveGUIDFromInstance(self, instance):
218        """
219            Returns the CedaGUID object associated with the given instance.
220            @param instance: an instance of CEDA_Observation os CEDA_ObservationCollection 
221        """
222        if instance is None or not hasattr(instance, 'id'):
223            return None
224        if type(instance) == CEDA_ObservationCollection:
225            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observationcollection==instance.id).first()
226        elif type(instance) == CEDA_Observation:
227            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observation==instance.id).first()
228        elif type(instance) == CEDA_Project:
229            return self._session.query(CedaGUID).filter(CedaGUID.ceda_project==instance.id).first()               
230   
231    def observationCollectionHasObservation(self, obs_coll_id, obs_id):
232        """
233            Checks if a CEDA_Collection contains a given CEDA_Observation.
234            @param obs_coll_id: the CEDA_ObservationColleciton id
235            @param obs_id: the CEDA_Observation id
236            @return: True if the collection contains the given observation, False otherwise 
237        """
238        coll = self._session.query(CEDA_ObservationCollection).filter(CEDA_ObservationCollection.id==obs_coll_id).first()
239        obs = self._session.query(CEDA_Observation).filter(CEDA_Observation.id==obs_id).first()
240        return obs in coll.member
241
242    def observationAuthor(self, observation):
243        """
244            Lists the CEDA_Observation author.
245            @param observation: the CEDA_Observation inside which look for the author           
246            @return: True if the collection contains the given observation, False otherwise 
247        """
248       
249        # TO FIX!!!
250        for partyInfo in observation.relatedParty:
251            if partyInfo.role == getCLValue(MM_RoleValue.cl_author):
252                return partyInfo.party       
253
254       
255
256    def extractObservationByTitleKeywords(self, keywords):
257        """
258            Loooks for CEDA_Observation containing a specific title (observation.identifier.code)
259            @param keywords: a space separated terms string
260            @return: a tuple containing a CEDA_Observation satisfying the queryllection.idenfitier element having the title 
261        """               
262        # search_vector is a ts_vector column. To search for terms, you use the
263        # @@ operator. plainto_tsquery turns a string into a query that can be
264        # used with @@. So this adds a where clause like "WHERE search_vector
265        # @@ plaint_tsquery(<search string>)"
266        q = self._session.query(CEDA_Observation). \
267            join(MO_Observation).join(MO_Observation.identifier). \
268            filter('md_identifier.code_search_vector @@ to_tsquery(:terms)')
269        # This binds the :terms placeholder to the searchterms string. User input
270        # should always be put into queries this way to prevent SQL injection.
271        q = q.params(terms=keywords)
272        return q.all()
273
274
275    def extractCollectionIdentifierByTitle(self, i_title):
276        """
277            Searches for an MD_Identifier from a CEDA_ObservationCollection contains a specific title (observation.identifier.code)
278            @param i_title: the CEDA_ObservationCollection.identifier.title value to search for
279            @return: a tuple containing a CEDA_ObservationCollection and the CEDA_ObservationCollection.idenfitier element having the title 
280        """
281        return self._session.query(CEDA_ObservationCollection, MD_Identifier). \
282            join(MO_ObservationCollection).join(MO_ObservationCollection.identifier). \
283            join(MD_Identifier.authority).filter(CI_Citation.title.like('%' + i_title + '%'))
284
285    def extractObservationsForProject(self, project):
286        """
287            Searches for the CEDA_Observation associated with a CEDA_Project
288            @param project: a CEDA_Project instance
289            @return: a tuple containing the associated CEDA_Observation 
290        """
291        return self._session.query(CEDA_Observation). \
292            join(CEDA_Observation, MO_Observation.inSupportOf).filter(CEDA_Project.id == project.id)
293
294    def extractProjectObservationCollections(self, project):
295        """
296            Searches for the Observation_Collections associated with a CEDA_Project
297            @param project: a CEDA_Project instance
298            @return: a tuple containing the associated CEDA_ObservationCollection 
299        """
300        mo_obs = self._session.query(MO_Observation).join(CEDA_Project).\
301            filter(CEDA_Project.id == project.id).subquery()     
302        obsers = self._session.query(CEDA_Observation).\
303            join(mo_obs, CEDA_Observation.id == mo_obs.c.id).one()
304       
305        '''
306        cos = self._session.query(CEDA_ObservationCollection).all()
307        co = self._session.query(MO_ObservationCollection).\
308            join(MO_ObservationCollection.member).\
309            filter(MO_ObservationCollection.member.contains(obsers))
310        '''
311        observations = self._session.query(MO_ObservationCollection).\
312            join(CEDA_Observation).\
313            filter(obsers.any(CEDA_Observation.id==obsers.c.id))
314        print "observation:" + str(observations.count())
315        return observations
316
317    def search(self, clazz, inst_id = None):
318        ret = EPB.search(clazz, inst_id, self._session)
319        return ret
320     
321    def searchSelectiveLoad(self, clazz, inst_id, attributes): 
322        """
323            Searches a required instance by id loading selectively \
324            the specified fields. The parameter "attributes" is a single string or a list of attributes
325            owned by the instance of "clazz". Furthermore such list may contain
326            also the children of the main attributes. For example "attrs" may look
327            like
328            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
329                      'relatedParty.party', 'result.source.function', 'permission', \
330                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
331                      'inSupportOf.abstract', 'dataLineage']
332            the first parameter refers to the main class so is equivalent to
333            clazz.resultAccumulation
334            the second parameter is equivalent to invoke
335            clazz.identifier.authority
336            As single string "attributes" could be as well just 'identifier.authority'
337            @param clazz: the class type to search for
338            @param inst_id: the instance id for which the search is done
339            @param attributes: a single string or a list of attributes to load
340            @param session: a session to use for the query. By default a new one is created automatically at start and closed at the end
341            @return the required instance             
342        """               
343        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
344        return ret   
345   
346    def loadAttributes(self, instance, attributes):
347        """
348            Returns the attribute of an instance. The parameter "attributes" is a single string or a list of attributes
349            owned by the instance of "clazz". Furthermore such list may contain
350            also the children of the main attributes. For example "attrs" may look
351            like
352            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
353                      'relatedParty.party', 'result.source.function', 'permission', \
354                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
355                      'inSupportOf.abstract', 'dataLineage']
356            the first parameter refers to the main class so is equivalent to
357            clazz.resultAccumulation
358            the second parameter is equivalent to invoke
359            clazz.identifier.authority
360            As single string "attributes" could be as well just 'identifier.authority'
361            @param instance: an instance containing the appropriate id
362            @param attributes: the attribute value required
363            @param session: the session to use for the operation
364            @return: the given instance filled with the required attributes.                     
365        """
366        instance = self._session.merge(instance)
367        EPB.loadAttributes(instance, attributes, self._session)                 
368        return instance
369
370    def executeNative(self, sqlNative):
371        return EPB.executeNative(sqlNative, self._session) 
372
373
374def getGeograpicExtentGeometry(ge):
375    '''
376        Creates the appropriate postgis geometry from a EX_GeographicExtent
377        @param ge: an EX_GeographicExtent instance
378        @return: a postgix text geometry
379    '''
380    if isinstance(ge, EX_GeographicBoundingBox):
381        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
382                       ge.eastBoundLongitude, ge.northBoundLatitude)
383    return None
384
385def _tmpstrftime(dt):
386    """
387        Returns a string from a datastring. This function is necessary because
388        python <3.2 strftime method is not able to handle date < 1900
389        @param dt: a datetime object
390    """
391    return "%s-%s-%s" % (dt.year, dt.month, dt.day)
392
393def unify_observation_collection_phenomenon_time(collection):
394    """
395        Returns the time period of the collections.member'a  phenomenonTime(s)
396        @param collection: an CEDA_ObservationColleciton instance
397        @return: a tuple (startDate, endDate) strings
398    """
399    dateFormat = '%Y-%m-%d'
400    ptStart = []
401    ptEnd = []     
402    for member in collection.member:
403        if member.phenomenonTime is None:
404            continue
405           
406        pt =  member.phenomenonTime
407        ptString = from_pt_to_string(pt)
408        if ptString[0] is not None:                   
409            ptStart.append(datetime.strptime(ptString[0], dateFormat))
410        if ptString[1] is not None:                   
411            ptEnd.append(datetime.strptime(ptString[1], dateFormat))
412    ptStart.sort()                   
413    ptEnd.sort()
414    start = None
415    end = None
416    #takes the earlier date
417    if len(ptStart) > 0:
418        start = _tmpstrftime(ptStart[0]) 
419    #takes the latest date
420    if len(ptEnd) > 0:
421        end = _tmpstrftime(ptEnd[len(ptEnd) - 1]) 
422    return start, end
Note: See TracBrowser for help on using the repository browser.