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

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

Complete - # 22528: Migration of FAtCat Open Search link for HPFeld
 http://team.ceda.ac.uk/trac/ceda/ticket/22528
Adds a ceda_timestamp column to the tm_instant table to simplify the search-by-time in cedamoles.
The code has been changed consequently to use the new parameter.

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 cedaMoles.MolesManager.db.tm_instant import customize_tm_instant
35from cedaMoles.MolesManager.db.search_indexes import init_search_indexes
36from cedaMoles.MolesManager.db.ceda_guid import init_ceda_guid
37from cedaMoles.MolesManager.db.after_flush import after_flush
38from ea_model.moles3_4.observationcollection.mo_observationcollection \
39    import MO_ObservationCollection
40from ea_model.moles3_4.observation.mo_observation import MO_Observation
41from sqlalchemy import event
42from cedaMoles.MolesManager.ceda_guid import CedaGUID
43from sqlalchemy.orm.collections import InstrumentedList
44from ea_model.iso_19115_2006_metadata_corrigendum.\
45    reference_system_information.md_identifier import MD_Identifier
46from ea_model.iso_19115_2006_metadata_corrigendum.\
47    citation_and_responsible_party_information.ci_citation import CI_Citation
48from ea_model.iso_19115_2006_metadata_corrigendum.\
49    extent_information.ex_geographicboundingbox import EX_GeographicBoundingBox
50from cedaMoles.libs.postgisutil import create_st_setSRID, unifyGeometriesAsBBox
51from ea_model.ceda_metadatamodel.ceda_observationcollection.\
52    ceda_observationcollection import CEDA_ObservationCollection
53from ea_model.ceda_metadatamodel.ceda_observation.\
54    ceda_observation import CEDA_Observation
55from ea_model.ceda_metadatamodel.ceda_project.ceda_project import CEDA_Project
56from cedaMoles.MolesManager.codelist import MM_RoleValue, getCLValue
57from ascore.utils import synchAttributes
58from cedaMoles.MolesManager.db.partyIndexes import associate_moparty_indexes
59
60class Moles3EPBFactory(EPB):   
61    def __init__(self, db_manager):
62        super(Moles3EPBFactory, self).__init__(db_manager)
63        self._init_ceda_customization()
64
65    def _init_ceda_customization(self):
66        init_ceda_guid(self._db_manager.metadata)       
67        associate_moparty_indexes(self._db_manager.metadata)
68        init_search_indexes(self._db_manager.engine)
69        customize_tm_instant(self._db_manager.engine, self._db_manager.metadata)
70       
71    def createEPB(self):
72        session = self._get_session()
73        event.listen(session, 'after_flush', after_flush)     
74        return Moles3EPB(session)
75
76class Moles3EPB(object):
77
78    def __init__(self, session):
79        self._session = session
80       
81    def close(self):
82        return self._session.close()       
83       
84    def searchEager(self, clazz, inst_id):
85        return EPB.searchEager(clazz, inst_id, self._session)     
86     
87    def _controlledCommit(self):
88        try:
89            self._session.commit()
90        except Exception as e:
91            print e
92
93    def rollback(self):
94        """
95        Rolls back the session and one a new transaction
96        """
97        EPB.rollback(self._session) 
98     
99    def persistInstance(self, instance):
100        """
101        Adds a new migration object.
102        **Parameters**
103            * instance: the object to add
104           
105        **Returns**
106            An updated, session independent, object instance reflecting
107            the new persisted object
108        """
109        EPB.persistInstance(instance, self._session)       
110
111    def mergeInstance(self, instance):
112        """
113        Copy the state an instance onto the persistent instance with the same identifier.
114        **Parameters**
115            * instance: the object to add
116           
117        **Returns**
118            An updated object instance reflecting the new persisted object
119        """
120        return EPB.mergeInstance(instance, self._session) 
121
122    def refresh(self, instance):
123        """
124        Expires and refresh the attributes on the given instance.
125        **Parameters**
126            * instance: the object to refresh
127        """
128        EPB.refresh(instance, self._session) 
129
130    def deleteInstance(self, instance, commit = False):
131        """
132            Deletes an object.
133           
134            **Parameters**
135            * `object` **instance**
136                the object to delete                           
137            * `bool` **commit**
138                Defines if the delete operation has to be immediately committed.
139                    Default is `False`
140        """ 
141        EPB.deleteInstance(instance, self._session, commit) 
142
143    def expunge(self, instance):
144        """
145        Expunges an object from the session
146        **Parameters**
147            * instance: the object to expunge
148        """
149        EPB.expunge(instance, self._session) 
150
151    def _mergeOrAddInSession(self, ceda_object):
152        try:
153            return self._session.merge(ceda_object)   
154        except:
155            self._session.add(ceda_object)
156            return ceda_object 
157     
158    def updateCedaObject(self, ceda_object, cols_to_update):
159        """
160            Update, eventually add to the session, and commit a CEDA Object in MOLES3 db.
161            @param ceda_object: the CEDA object to update
162            @param cols_to_update: a dictionary containing the columns to update for the given ceda_object and the desired value.
163            If the attribute is a list of objects the new instances are appended only if do not exist in the actual list
164            @return: the given instance with the updated attributes.
165        """
166        coll = self._mergeOrAddInSession(ceda_object)
167        if coll != None:       
168            for k,v in cols_to_update.items():
169                if v is None:
170                    continue
171                if hasattr(coll, k):                   
172                    coll_k = getattr(coll, k)                       
173                    if type(coll_k) == list or type(coll_k) == InstrumentedList:
174                        tmp_coll = []
175                        if type(v) == list or type(v) == InstrumentedList:
176                            tmp_coll.extend(v)
177                        else:
178                            tmp_coll.append(v)
179                        for item in tmp_coll:
180                            el = self._mergeOrAddInSession(item)
181                            if el not in coll_k:
182                                coll_k.append(el)
183                    else:
184                        el = self._mergeOrAddInSession(v)
185                        setattr(coll, k, el)
186        synchAttributes(coll)                                             
187        self._controlledCommit()
188        #return coll                                     
189
190    def getUnifyObservationCollectionGEAsBBox(self, collection):
191        """
192            Returns the union of the collections.member'a  GeographicExtension(s)
193            @param collection: an CEDA_ObservationColleciton instance 
194        """
195        if not hasattr(collection, 'member'):
196            return None
197       
198        bboxes = []
199        for member in collection.member:
200            for ge in member.geographicExtent:
201                bboxes.append(getGeograpicExtentGeometry(ge))
202       
203        return unifyGeometriesAsBBox(bboxes, self) 
204       
205        #return unifyGeometriesAsBBox(bboxes, self)
206
207
208    def retrieveGUIDFromInstance(self, instance):
209        """
210            Returns the CedaGUID object associated with the given instance.
211            @param instance: an instance of CEDA_Observation os CEDA_ObservationCollection 
212        """
213        if instance is None or not hasattr(instance, 'id'):
214            return None
215        if type(instance) == CEDA_ObservationCollection:
216            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observationcollection==instance.id).first()
217        elif type(instance) == CEDA_Observation:
218            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observation==instance.id).first()
219        elif type(instance) == CEDA_Project:
220            return self._session.query(CedaGUID).filter(CedaGUID.ceda_project==instance.id).first()               
221   
222    def observationCollectionHasObservation(self, obs_coll_id, obs_id):
223        """
224            Checks if a CEDA_Collection contains a given CEDA_Observation.
225            @param obs_coll_id: the CEDA_ObservationColleciton id
226            @param obs_id: the CEDA_Observation id
227            @return: True if the collection contains the given observation, False otherwise 
228        """
229        coll = self._session.query(CEDA_ObservationCollection).filter(CEDA_ObservationCollection.id==obs_coll_id).first()
230        obs = self._session.query(CEDA_Observation).filter(CEDA_Observation.id==obs_id).first()
231        return obs in coll.member
232
233    def observationAuthor(self, observation):
234        """
235            Lists the CEDA_Observation author.
236            @param observation: the CEDA_Observation inside which look for the author           
237            @return: True if the collection contains the given observation, False otherwise 
238        """
239       
240        # TO FIX!!!
241        for partyInfo in observation.relatedParty:
242            if partyInfo.role == getCLValue(MM_RoleValue.cl_author):
243                return partyInfo.party       
244
245       
246
247    def extractObservationByTitleKeywords(self, keywords):
248        """
249            Loooks for CEDA_Observation containing a specific title (observation.identifier.code)
250            @param keywords: a space separated terms string
251            @returns: dictionary where the keys are the GUID of the CEDA_Observation in the value 
252        """               
253        # search_vector is a ts_vector column. To search for terms, you use the
254        # @@ operator. plainto_tsquery turns a string into a query that can be
255        # used with @@. So this adds a where clause like "WHERE search_vector
256        # @@ plaint_tsquery(<search string>)"
257        q = self._session.query(CEDA_Observation). \
258            join(MO_Observation).join(MO_Observation.identifier). \
259            filter('md_identifier.code_search_vector @@ to_tsquery(:terms)')
260        # This binds the :terms placeholder to the searchterms string. User input
261        # should always be put into queries this way to prevent SQL injection.
262        q = q.params(terms=keywords)
263       
264        ret = {}
265        for item in q.all():
266            guid = self.retrieveGUIDFromInstance(item)
267            if guid is not None:
268                ret[guid.id] = item           
269        return ret
270        return 
271
272
273    def extractCollectionIdentifierByTitle(self, i_title):
274        """
275            Searches for an MD_Identifier from a CEDA_ObservationCollection contains a specific title (observation.identifier.code)
276            @param i_title: the CEDA_ObservationCollection.identifier.title value to search for
277            @return: a tuple containing a CEDA_ObservationCollection and the CEDA_ObservationCollection.idenfitier element having the title 
278        """
279        return self._session.query(CEDA_ObservationCollection, MD_Identifier). \
280            join(MO_ObservationCollection).join(MO_ObservationCollection.identifier). \
281            join(MD_Identifier.authority).filter(CI_Citation.title.like('%' + i_title + '%'))
282
283    def extractObservationsForProject(self, project):
284        """
285            Searches for the CEDA_Observation associated with a CEDA_Project
286            @param project: a CEDA_Project instance
287            @return: a tuple containing the associated CEDA_Observation 
288        """
289        return self._session.query(CEDA_Observation). \
290            join(CEDA_Observation, MO_Observation.inSupportOf).filter(CEDA_Project.id == project.id)
291
292    def extractProjectObservationCollections(self, project):
293        """
294            Searches for the Observation_Collections associated with a CEDA_Project
295            @param project: a CEDA_Project instance
296            @return: a tuple containing the associated CEDA_ObservationCollection 
297        """
298        mo_obs = self._session.query(MO_Observation).join(CEDA_Project).\
299            filter(CEDA_Project.id == project.id).subquery()     
300        obsers = self._session.query(CEDA_Observation).\
301            join(mo_obs, CEDA_Observation.id == mo_obs.c.id).one()
302       
303        '''
304        cos = self._session.query(CEDA_ObservationCollection).all()
305        co = self._session.query(MO_ObservationCollection).\
306            join(MO_ObservationCollection.member).\
307            filter(MO_ObservationCollection.member.contains(obsers))
308        '''
309        observations = self._session.query(MO_ObservationCollection).\
310            join(CEDA_Observation).\
311            filter(obsers.any(CEDA_Observation.id==obsers.c.id))
312        print "observation:" + str(observations.count())
313        return observations
314
315    def search(self, clazz, inst_id = None):
316        ret = EPB.search(clazz, inst_id, self._session)
317        return ret
318     
319    def search_object_and_gui(self, clazz, inst_id = None):       
320        result = EPB.search(clazz, inst_id, self._session)
321        ret = {}
322        for item in result.all():
323            guid = self.retrieveGUIDFromInstance(item)
324            if guid is not None:
325                ret[guid.id] = item           
326        return ret     
327     
328    def searchSelectiveLoad(self, clazz, inst_id, attributes): 
329        """
330            Searches a required instance by id loading selectively \
331            the specified fields. The parameter "attributes" is a single string or a list of attributes
332            owned by the instance of "clazz". Furthermore such list may contain
333            also the children of the main attributes. For example "attrs" may look
334            like
335            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
336                      'relatedParty.party', 'result.source.function', 'permission', \
337                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
338                      'inSupportOf.abstract', 'dataLineage']
339            the first parameter refers to the main class so is equivalent to
340            clazz.resultAccumulation
341            the second parameter is equivalent to invoke
342            clazz.identifier.authority
343            As single string "attributes" could be as well just 'identifier.authority'
344            @param clazz: the class type to search for
345            @param inst_id: the instance id for which the search is done
346            @param attributes: a single string or a list of attributes to load
347            @param session: a session to use for the query. By default a new one is created automatically at start and closed at the end
348            @return the required instance             
349        """               
350        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
351        return ret   
352   
353    def loadAttributes(self, instance, attributes):
354        """
355            Returns the attribute of an instance. The parameter "attributes" is a single string or a list of attributes
356            owned by the instance of "clazz". Furthermore such list may contain
357            also the children of the main attributes. For example "attrs" may look
358            like
359            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
360                      'relatedParty.party', 'result.source.function', 'permission', \
361                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
362                      'inSupportOf.abstract', 'dataLineage']
363            the first parameter refers to the main class so is equivalent to
364            clazz.resultAccumulation
365            the second parameter is equivalent to invoke
366            clazz.identifier.authority
367            As single string "attributes" could be as well just 'identifier.authority'
368            @param instance: an instance containing the appropriate id
369            @param attributes: the attribute value required
370            @param session: the session to use for the operation
371            @return: the given instance filled with the required attributes.                     
372        """
373        instance = self._session.merge(instance)
374        EPB.loadAttributes(instance, attributes, self._session)                 
375        return instance
376
377    def executeNative(self, sqlNative):
378        return EPB.executeNative(sqlNative, self._session) 
379
380
381def getGeograpicExtentGeometry(ge):
382    '''
383        Creates the appropriate postgis geometry from a EX_GeographicExtent
384        @param ge: an EX_GeographicExtent instance
385        @return: a postgix text geometry
386    '''
387    if isinstance(ge, EX_GeographicBoundingBox):
388        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
389                       ge.eastBoundLongitude, ge.northBoundLatitude)
390    return None
391
392def _tmpstrftime(dt):
393    """
394        Returns a string from a datastring. This function is necessary because
395        python <3.2 strftime method is not able to handle date < 1900
396        @param dt: a datetime object
397    """
398    return "%s-%s-%s" % (dt.year, dt.month, dt.day)
Note: See TracBrowser for help on using the repository browser.