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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/mauRepo/MolesManager/trunk/cedaMoles/MolesManager/moles3epb.py@8501
Revision 8501, 19.9 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
Return a dictionary of objects where the key is the GUID of the object in the value

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 search_object_and_gui(self, clazz, inst_id = None):       
322        result = EPB.search(clazz, inst_id, self._session)
323        ret = {}
324        for item in result:
325            guid = self.retrieveGUIDFromInstance(item)
326            if guid is not None:
327                ret[guid.id] = item           
328        return ret     
329     
330    def searchSelectiveLoad(self, clazz, inst_id, attributes): 
331        """
332            Searches a required instance by id loading selectively \
333            the specified fields. The parameter "attributes" is a single string or a list of attributes
334            owned by the instance of "clazz". Furthermore such list may contain
335            also the children of the main attributes. For example "attrs" may look
336            like
337            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
338                      'relatedParty.party', 'result.source.function', 'permission', \
339                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
340                      'inSupportOf.abstract', 'dataLineage']
341            the first parameter refers to the main class so is equivalent to
342            clazz.resultAccumulation
343            the second parameter is equivalent to invoke
344            clazz.identifier.authority
345            As single string "attributes" could be as well just 'identifier.authority'
346            @param clazz: the class type to search for
347            @param inst_id: the instance id for which the search is done
348            @param attributes: a single string or a list of attributes to load
349            @param session: a session to use for the query. By default a new one is created automatically at start and closed at the end
350            @return the required instance             
351        """               
352        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
353        return ret   
354   
355    def loadAttributes(self, instance, attributes):
356        """
357            Returns the attribute of an instance. The parameter "attributes" is a single string or a list of attributes
358            owned by the instance of "clazz". Furthermore such list may contain
359            also the children of the main attributes. For example "attrs" may look
360            like
361            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
362                      'relatedParty.party', 'result.source.function', 'permission', \
363                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
364                      'inSupportOf.abstract', 'dataLineage']
365            the first parameter refers to the main class so is equivalent to
366            clazz.resultAccumulation
367            the second parameter is equivalent to invoke
368            clazz.identifier.authority
369            As single string "attributes" could be as well just 'identifier.authority'
370            @param instance: an instance containing the appropriate id
371            @param attributes: the attribute value required
372            @param session: the session to use for the operation
373            @return: the given instance filled with the required attributes.                     
374        """
375        instance = self._session.merge(instance)
376        EPB.loadAttributes(instance, attributes, self._session)                 
377        return instance
378
379    def executeNative(self, sqlNative):
380        return EPB.executeNative(sqlNative, self._session) 
381
382
383def getGeograpicExtentGeometry(ge):
384    '''
385        Creates the appropriate postgis geometry from a EX_GeographicExtent
386        @param ge: an EX_GeographicExtent instance
387        @return: a postgix text geometry
388    '''
389    if isinstance(ge, EX_GeographicBoundingBox):
390        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
391                       ge.eastBoundLongitude, ge.northBoundLatitude)
392    return None
393
394def _tmpstrftime(dt):
395    """
396        Returns a string from a datastring. This function is necessary because
397        python <3.2 strftime method is not able to handle date < 1900
398        @param dt: a datetime object
399    """
400    return "%s-%s-%s" % (dt.year, dt.month, dt.day)
401
402def unify_observation_collection_phenomenon_time(collection):
403    """
404        Returns the time period of the collections.member'a  phenomenonTime(s)
405        @param collection: an CEDA_ObservationColleciton instance
406        @return: a tuple (startDate, endDate) strings
407    """
408    dateFormat = '%Y-%m-%d'
409    ptStart = []
410    ptEnd = []     
411    for member in collection.member:
412        if member.phenomenonTime is None:
413            continue
414           
415        pt =  member.phenomenonTime
416        ptString = from_pt_to_string(pt)
417        if ptString[0] is not None:                   
418            ptStart.append(datetime.strptime(ptString[0], dateFormat))
419        if ptString[1] is not None:                   
420            ptEnd.append(datetime.strptime(ptString[1], dateFormat))
421    ptStart.sort()                   
422    ptEnd.sort()
423    start = None
424    end = None
425    #takes the earlier date
426    if len(ptStart) > 0:
427        start = _tmpstrftime(ptStart[0]) 
428    #takes the latest date
429    if len(ptEnd) > 0:
430        end = _tmpstrftime(ptEnd[len(ptEnd) - 1]) 
431    return start, end
Note: See TracBrowser for help on using the repository browser.