source: mauRepo/MolesManager/trunk/src/MolesManager/moles3epb.py @ 8434

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

Incomplete - # 22416: CEDA MOLES3 - search function - by title keyword
 http://team.ceda.ac.uk/trac/ceda/ticket/22416

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 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
37from sqlalchemy.orm import mapper
38from 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 libs.postgisutil import unifyGeometries, create_st_setSRID, getBox2D,\
44    unifyGeometriesAsBBox
45from ea_model.ceda_metadatamodel.ceda_observationcollection.ceda_observationcollection import CEDA_ObservationCollection
46from ea_model.ceda_metadatamodel.ceda_observation.ceda_observation import CEDA_Observation
47from ea_model.ceda_metadatamodel.ceda_project.ceda_project import CEDA_Project
48from ea_model.moles3_4.utilities.mo_responsiblepartyinfo import MO_ResponsiblePartyInfo
49from ea_model.moles3_4.utilities.ceda_rolevalue import CEDA_RoleValue
50from MolesManager.codelist import MM_RoleValue, getCLValue
51
52class Moles3EPBFactory(EPB):
53   
54    def __init__(self, dbManager):
55        self._dbManager = dbManager
56        self._initCEDA_Customization()   
57       
58
59    def _initCEDA_Customization(self):
60        self._associateCEDA_GUID()
61        self._initSearchIndexes()       
62       
63    def _associateCEDA_GUID(self):
64        guid_table = Table('ceda_guid', self._dbManager.metadata, \
65                           Column('id', String, primary_key=True), \
66                           Column('ceda_observationcollection', Integer, ForeignKey('ceda_observationcollection.id')), 
67                           Column('ceda_observation', Integer, ForeignKey('ceda_observation.id')))
68        mapper(CedaGUID, guid_table)
69        self._dbManager.metadata.create_all()
70
71    def _initSearchIndexes(self):
72        #To Be Done - CHECK IF THE COLUMN ALREADY EXISTS!
73        # We don't want sqlalchemy to know about this column so we add it externally.
74        try:
75            self._dbManager.engine.execute("alter table md_identifier add column code_search_vector tsvector")                 
76
77            # This indexes the tsvector column
78
79            self._dbManager.engine.execute("create index md_identifier_code_search_index on md_identifier using gin(code_search_vector)")
80
81            # This sets up the trigger that keeps the tsvector column up to date.
82            self._dbManager.engine.execute("create trigger md_identifier_code_search_update before update or insert on md_identifier \
83                for each row execute procedure tsvector_update_trigger('code_search_vector', 'pg_catalog.english', code)")                       
84        except Exception as e:
85            pass
86
87    def _getSession(self):
88        if self._dbManager is not None:
89            return self._dbManager.createDbSession()               
90        return None
91       
92    def createEPB(self):
93        return Moles3EPB(self._getSession())
94
95class Moles3EPB(object):
96
97    def __init__(self, session):
98        self._session = session
99       
100    def close(self):
101        return self._session.close()       
102       
103    def searchEager(self, clazz, inst_id):
104        return EPB.searchEager(clazz, inst_id, self._session)     
105     
106    def _controlledCommit(self):
107        try:
108            self._session.commit()
109        except Exception as e:
110            print e
111     
112    def persistInstance(self, instance):
113        """
114            Adds a new migration object.
115            @param migrationObj: the migration object to add
116            @param session: an SQLAlchemy Session object. If not None the session IS NOT closed at the exit,
117            If None (default) a new Session is created from the underlying EPB and closed at the exit.
118            @return an updated, session independent, object instance reflecting the new persisted object
119        """
120        self._session.add(instance)
121        self._session.commit()     
122       
123     
124    def updateCedaObject(self, ceda_object, cols_to_update):
125        """
126            Update, eventually add to the session, and commit a CEDA Object in MOLES3 db.
127            @param ceda_object: the CEDA object to update
128            @param cols_to_update: a dictionary containing the columns to update for the given ceda_object and the desired value.
129            If the attribute is a list of objects the new instances are appended only if do not exist in the actual list
130            @return: the given instance with the updated attributes.
131        """
132        coll = None
133        try:
134            if ceda_object in self._session:
135                coll = self._session.merge(ceda_object)
136            else:
137                self._session.add(ceda_object)
138                coll = ceda_object   
139        except Exception as e:
140            print e
141        if coll != None:       
142            for k,v in cols_to_update.items():
143                if hasattr(coll, k):
144                    val = None
145                    try:
146                        val = self._session.merge(v)
147                    except Exception:
148                        val = v
149                    coll_k = getattr(coll, k)                       
150                    if type(coll_k) == list or type(coll_k) == InstrumentedList:
151                        tmp_coll = []
152                        if type(val) == list or type(val) == InstrumentedList:
153                            tmp_coll.extend(val)
154                        else:
155                            tmp_coll.append(val)
156                        for item in tmp_coll:
157                            if item not in coll_k:
158                                coll_k.append(item)
159                    else:
160                        setattr(coll, k, val)
161        self._controlledCommit()
162        return coll                                     
163
164    def getUnifyObservationCollectionGEAsBBox(self, collection):
165        """
166            Returns the union of the collections.member'a  GeographicExtension(s)
167            @param collection: an CEDA_ObservationColleciton instance 
168        """
169        bboxes = []
170        for member in collection.member:
171            for ge in member.geographicExtent:
172                bboxes.append(getGeograpicExtentGeometry(ge))
173       
174        return unifyGeometriesAsBBox(bboxes, self) 
175       
176
177    def retrieveGUIDFromInstance(self, instance):
178        """
179            Returns the CedaGUID object associated with the given instance.
180            @param instance: an instance of CEDA_Observation os CEDA_ObservationCollection 
181        """
182        if instance is None or not hasattr(instance, 'id'):
183            return None
184        if type(instance) == CEDA_ObservationCollection:
185            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observationcollection==instance.id).first()
186        elif type(instance) == CEDA_Observation:
187            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observation==instance.id).first()       
188   
189    def observationCollectionHasObservation(self, obs_coll_id, obs_id):
190        """
191            Checks if a CEDA_Collection contains a given CEDA_Observation.
192            @param obs_coll_id: the CEDA_ObservationColleciton id
193            @param obs_id: the CEDA_Observation id
194            @return: True if the collection contains the given observation, False otherwise 
195        """
196        coll = self._session.query(CEDA_ObservationCollection).filter(CEDA_ObservationCollection.id==obs_coll_id).first()
197        obs = self._session.query(CEDA_Observation).filter(CEDA_Observation.id==obs_id).first()
198        return obs in coll.member
199
200    def observationAuthor(self, observation):
201        """
202            Lists the CEDA_Observation author.
203            @param observation: the CEDA_Observation inside which look for the author           
204            @return: True if the collection contains the given observation, False otherwise 
205        """
206       
207        # TO FIX!!!
208        for partyInfo in observation.relatedParty:
209            if partyInfo.role == getCLValue(MM_RoleValue.cl_author):
210                return partyInfo.party       
211
212       
213
214    def extractObservationByTitleKeywords(self, keywords):
215        """
216            Loooks for CEDA_Observation containing a specific title (observation.identifier.code)
217            @param keywords: a space separated terms string
218            @return: a tuple containing a CEDA_Observation satisfying the queryllection.idenfitier element having the title 
219        """               
220        # search_vector is a ts_vector column. To search for terms, you use the
221        # @@ operator. plainto_tsquery turns a string into a query that can be
222        # used with @@. So this adds a where clause like "WHERE search_vector
223        # @@ plaint_tsquery(<search string>)"
224        q = self._session.query(CEDA_Observation). \
225            join(MO_Observation).join(MO_Observation.identifier). \
226            filter('md_identifier.code_search_vector @@ to_tsquery(:terms)')
227        # This binds the :terms placeholder to the searchterms string. User input
228        # should always be put into queries this way to prevent SQL injection.
229        q = q.params(terms=keywords)
230        return q.all()
231
232
233    def extractCollectionIdentifierByTitle(self, i_title):
234        """
235            Searches for an MD_Identifier from a CEDA_ObservationCollection contains a specific title (observation.identifier.code)
236            @param i_title: the CEDA_ObservationCollection.identifier.title value to search for
237            @return: a tuple containing a CEDA_ObservationCollection and the CEDA_ObservationCollection.idenfitier element having the title 
238        """
239        return self._session.query(CEDA_ObservationCollection, MD_Identifier). \
240            join(MO_ObservationCollection).join(MO_ObservationCollection.identifier). \
241            join(MD_Identifier.authority).filter(CI_Citation.title.like('%' + i_title + '%'))
242
243    def extractObservationsForProject(self, project):
244        """
245            Searches for the CEDA_Observation associated with a CEDA_Project
246            @param project: a CEDA_Project instance
247            @return: a tuple containing the associated CEDA_Observation 
248        """
249        return self._session.query(CEDA_Observation). \
250            join(CEDA_Observation, MO_Observation.inSupportOf).filter(CEDA_Project.id == project.id)
251
252    def extractProjectObservationCollections(self, project):
253        """
254            Searches for the Observation_Collections associated with a CEDA_Project
255            @param project: a CEDA_Project instance
256            @return: a tuple containing the associated CEDA_ObservationCollection 
257        """
258        mo_obs = self._session.query(MO_Observation).join(CEDA_Project).filter(CEDA_Project.id == project.id).subquery()     
259        obsers = self._session.query(CEDA_Observation).join(mo_obs, CEDA_Observation.id == mo_obs.c.id).one()
260        #print "obsers: " + str(intSession.query(CEDA_Observation).join(mo_obs, CEDA_Observation.id == mo_obs.c.id).count())
261       
262        cos = self._session.query(CEDA_ObservationCollection).all()
263        co = self._session.query(MO_ObservationCollection).join(MO_ObservationCollection.member).filter(MO_ObservationCollection.member.contains(obsers))
264       
265        observations = self._session.query(MO_ObservationCollection).join(CEDA_Observation). \
266            filter(obsers.any(CEDA_Observation.id==obsers.c.id))
267        print "observation:" + str(observations.count())
268        return observations
269
270    def search(self, clazz, inst_id):
271        ret = EPB.search(clazz, inst_id, self._session)
272        return ret
273     
274    def searchSelectiveLoad(self, clazz, inst_id, attributes): 
275        """
276            Searches a required instance by id loading selectively \
277            the specified fields. The parameter "attributes" is a single string or a list of attributes
278            owned by the instance of "clazz". Furthermore such list may contain
279            also the children of the main attributes. For example "attrs" may look
280            like
281            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
282                      'relatedParty.party', 'result.source.function', 'permission', \
283                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
284                      'inSupportOf.abstract', 'dataLineage']
285            the first parameter refers to the main class so is equivalent to
286            clazz.resultAccumulation
287            the second parameter is equivalent to invoke
288            clazz.identifier.authority
289            As single string "attributes" could be as well just 'identifier.authority'
290            @param clazz: the class type to search for
291            @param inst_id: the instance id for which the search is done
292            @param attributes: a single string or a list of attributes to load
293            @param session: a session to use for the query. By default a new one is created automatically at start and closed at the end
294            @return the required instance             
295        """               
296        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
297        return ret   
298   
299    def loadAttributes(self, instance, attributes):
300        """
301            Returns the attribute of an instance. The parameter "attributes" is a single string or a list of attributes
302            owned by the instance of "clazz". Furthermore such list may contain
303            also the children of the main attributes. For example "attrs" may look
304            like
305            ['resultAccumulation', 'identifier.authority', 'resultTime.position.dateTime8601.month', \
306                      'relatedParty.party', 'result.source.function', 'permission', \
307                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
308                      'inSupportOf.abstract', 'dataLineage']
309            the first parameter refers to the main class so is equivalent to
310            clazz.resultAccumulation
311            the second parameter is equivalent to invoke
312            clazz.identifier.authority
313            As single string "attributes" could be as well just 'identifier.authority'
314            @param instance: an instance containing the appropriate id
315            @param attributes: the attribute value required
316            @param session: the session to use for the operation
317            @return: the given instance filled with the required attributes.                     
318        """
319        instance = self._session.merge(instance)
320        EPB.loadAttributes(instance, attributes, self._session)                 
321        return instance
322
323    def executeNative(self, sqlNative):
324        return EPB.executeNative(sqlNative, self._session) 
325
326
327def getGeograpicExtentGeometry(ge):
328    '''
329        Creates the appropriate postgis geometry from a EX_GeographicExtent
330        @param ge: an EX_GeographicExtent instance
331        @return: a postgix text geometry
332    '''
333    if isinstance(ge, EX_GeographicBoundingBox):
334        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
335                       ge.eastBoundLongitude, ge.northBoundLatitude)
336    return None
Note: See TracBrowser for help on using the repository browser.