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

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

Incomplete - # 22490: CEDA Observation Collection - Description
 http://team.ceda.ac.uk/trac/ceda/ticket/22490

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
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
47
48class Moles3EPBFactory(EPB):
49   
50    def __init__(self, dbManager):
51        self._dbManager = dbManager
52        self._initCEDA_Customization()   
53       
54
55    def _initCEDA_Customization(self):
56        self._associateCEDA_GUID()
57        self._initSearchIndexes()       
58       
59    def _associateCEDA_GUID(self):
60        guid_table = Table('ceda_guid', self._dbManager.metadata, \
61                           Column('id', String, primary_key=True), \
62                           Column('ceda_observationcollection', Integer, ForeignKey('ceda_observationcollection.id')), 
63                           Column('ceda_observation', Integer, ForeignKey('ceda_observation.id')))
64        mapper(CedaGUID, guid_table)
65        self._dbManager.metadata.create_all()
66
67    def _initSearchIndexes(self):
68        #To Be Done - CHECK IF THE COLUMN ALREADY EXISTS!
69        # We don't want sqlalchemy to know about this column so we add it externally.
70        try:
71            self._dbManager.engine.execute("alter table md_identifier add column code_search_vector tsvector")                 
72
73            # This indexes the tsvector column
74
75            self._dbManager.engine.execute("create index md_identifier_code_search_index on md_identifier using gin(code_search_vector)")
76
77            # This sets up the trigger that keeps the tsvector column up to date.
78            self._dbManager.engine.execute("create trigger md_identifier_code_search_update before update or insert on md_identifier \
79                for each row execute procedure tsvector_update_trigger('code_search_vector', 'pg_catalog.english', code)")                       
80        except Exception as e:
81            pass
82
83    def _getSession(self):
84        if self._dbManager is not None:
85            return self._dbManager.createDbSession()               
86        return None
87       
88    def createEPB(self):
89        return Moles3EPB(self._getSession())
90
91class Moles3EPB(object):
92
93    def __init__(self, session):
94        self._session = session
95       
96    def close(self):
97        return self._session.close()       
98       
99    def searchEager(self, clazz, inst_id):
100        return EPB.searchEager(clazz, inst_id, self._session)     
101     
102    def persistInstance(self, instance):
103        """
104            Adds a new migration object.
105            @param migrationObj: the migration object to add
106            @param session: an SQLAlchemy Session object. If not None the session IS NOT closed at the exit,
107            If None (default) a new Session is created from the underlying EPB and closed at the exit.
108            @return an updated, session independent, object instance reflecting the new persisted object
109        """     
110        EPB.persistInstance(instance, self._session)
111       
112     
113    def updateCedaObject(self, ceda_object, cols_to_update):
114        """
115            Update and eventually commit a CEDA Object in MOLES3 db.
116            @param ceda_object: the CEDA object to update
117            @param cols_to_update: a dictionary containing the columns to update for the given ceda_object and the desired value.
118            If the attribute is a list of objects the new instances are appended only if do not exist in the actual list
119            @return: the given instance with the updated attributes.
120        """
121        coll = None
122        try:
123            coll = self._session.merge(ceda_object)
124        except Exception as e:
125            print e
126        if coll != None:       
127            for k,v in cols_to_update.items():
128                if hasattr(coll, k):
129                    val = None
130                    try:
131                        val = self._session.merge(v)
132                    except Exception:
133                        val = v
134                    coll_k = getattr(coll, k)                       
135                    if type(coll_k) == list or type(coll_k) == InstrumentedList:
136                        tmp_coll = []
137                        if type(val) == list or type(val) == InstrumentedList:
138                            tmp_coll.extend(val)
139                        else:
140                            tmp_coll.append(val)
141                        for item in tmp_coll:
142                            if item not in coll_k:
143                                coll_k.append(item)
144                    else:
145                        setattr(coll, k, val)
146        self._session.commit()
147        return coll                                     
148
149    def getGeograpicExtentGeometry(self, ge):
150        '''
151            Creates the appropriate postgis geometry from a EX_GeographicExtent
152            @param ge: an EX_GeographicExtent instance
153            @return: a postgix text geometry
154        '''
155        if isinstance(ge, EX_GeographicBoundingBox):
156            return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
157                           ge.eastBoundLongitude, ge.northBoundLatitude)
158        return None
159   
160   
161   
162    def getUnifyObservationCollectionGE(self, collection):
163        """
164            Returns the union of the collections.member'a  GeographicExtension(s)
165            @param collection: an CEDA_ObservationColleciton instance 
166        """
167        bboxes = []
168        for member in collection.member:
169            for ge in member.geographicExtent:
170                bboxes.append(self.getGeograpicExtentGeometry(ge))
171       
172        return unifyGeometries(bboxes, self) 
173
174    def retrieveGUIDFromInstance(self, instance):
175        """
176            Returns the CedaGUID object associated with the given instance.
177            @param instance: an instance of CEDA_Observation os CEDA_ObservationCollection 
178        """
179        if instance is None or not hasattr(instance, 'id'):
180            return None
181        if type(instance) == CEDA_ObservationCollection:
182            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observationcollection==instance.id).first()
183        elif type(instance) == CEDA_Observation:
184            return self._session.query(CedaGUID).filter(CedaGUID.ceda_observation==instance.id).first()       
185   
186    def observationCollectionHasObservation(self, obs_coll_id, obs_id):
187        """
188            Checks if a CEDA_Collection contains a given CEDA_Observation.
189            @param obs_coll_id: the CEDA_ObservationColleciton id
190            @param obs_id: the CEDA_Observation id
191            @return: True if the collection contains the given observation, False otherwise 
192        """
193        coll = self._session.query(CEDA_ObservationCollection).filter(CEDA_ObservationCollection.id==obs_coll_id).first()
194        obs = self._session.query(CEDA_Observation).filter(CEDA_Observation.id==obs_id).first()
195        return obs in coll.member
196
197    def observationAuthor(self, obs_id):
198        """
199            Lists the CEDA_Observation author.
200            @param obs_id: the CEDA_Observation id           
201            @return: True if the collection contains the given observation, False otherwise 
202        """
203       
204        # TO FIX!!!
205       
206        '''
207        ret = self._session.query(MO_ResponsiblePartyInfo).join(MO_Observation). \
208            filter(MO_ResponsiblePartyInfo.role == CEDA_RoleValue.cl_author). \
209            filter(MO_Observation.id == obs_id)       
210        return ret
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_ObservationCollection.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) 
Note: See TracBrowser for help on using the repository browser.