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

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

Incomplete - # 22488: CEDA Observation Collection - Geographical Extent
 http://team.ceda.ac.uk/trac/ceda/ticket/22488

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