source: ndgCommon/trunk/ndg/common/src/clients/xmldb/eXist/feedclient.py @ 4983

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/ndgCommon/trunk/ndg/common/src/clients/xmldb/eXist/feedclient.py@4983
Revision 4983, 9.3 KB checked in by cbyrom, 11 years ago (diff)

Add a new feed for collecting corrections info submitted by users of
the atom editor + create new method on client to do this + add new
tests and tidy up some code.

Line 
1'''
2 Class supporting set up and communication with eXist DB
3 for the purposes of creating and updating atom feeds
4 
5 @author: C Byrom - Tessella 09
6'''
7import logging, httplib
8import feedparser
9from ndg.common.src.models.vocabtermdata import VocabTermData as VTD
10from ndg.common.src.models.ndgObject import ndgObject
11import ndg.common.src.clients.xmldb.eXist.dbconstants as dc
12
13
14class FeedClientError(Exception):
15    """
16    Exception handling for when a problem is found with an eXist hosted atom feed
17    """
18    def __init__(self, msg):
19        logging.error(msg)
20        Exception.__init__(self, msg)
21
22class FeedClient(object):
23   
24    TOPLEVEL_ATOM_FEED_TITLE = 'Moles Atom Data'
25    TOPLEVEL_DIF_FEED_TITLE = 'DIF Data'
26    ENTITYLEVEL_ATOM_FEED_TITLE = 'Moles %s Atom Data'
27    DEPLOYMENTS_ATOM_FEED_TITLE = 'Deployments Data (activities/observation stations/data production tools)'
28    PROVIDER_SUFFIX = ' - for provider, %s'
29    PROVIDERLEVEL_ATOM_FEED_TITLE = TOPLEVEL_ATOM_FEED_TITLE + PROVIDER_SUFFIX
30    PROVIDERLEVEL_DIF_FEED_TITLE = TOPLEVEL_DIF_FEED_TITLE + PROVIDER_SUFFIX
31    CORRECTIONS_FEED_TITLE = 'Corrections data submitted by atom editor users'
32    DIF_ENTRY_TITLE = 'DIF Record'
33   
34    REST_BASE_URL = '/exist/rest'
35    FEED_ERROR_MSG = 'Failed to retrieve valid feed - error'
36    FEED_STRING = '<?xml version="1.0" ?><feed xmlns="http://www.w3.org/2005/Atom"> \
37        <title>%s</title></feed>'
38   
39    def __init__(self, auth, dbHostName = None, eXistPortNo = '8080'):
40        '''
41        Initialise a client to the eXist atom feed services
42        @param auth: Basic base64 encoded authentication details for the DB
43        @keyword dbHostName: name of eXist DB to use
44        @keyword eXistPortNo: Port number that the eXist DB is exposed by - defaults
45        to '8080' if not set
46        '''
47        logging.info("Initialising eXist feed client")
48        self.auth = auth
49        self.hostAndPort = '%s:%s' %(dbHostName, eXistPortNo)
50        logging.info("eXist feed client initialised")
51
52
53    def __postEditAtomFeed(self, collection, atomXMLString):
54        '''
55        Post an atom to the eXist atom feed 'edit' interface for the
56        specified collection
57        - if the atom is a feed, this will set up the feed, if it doesn't already
58        exist
59        - if the atom is an entry, this will add an entry to the feed for the
60        collection, if it exists
61        @param collection: eXist collection to associate the atom doc with
62        @param atomXMLString: a string containing an atom doc in XML format
63        @raise FeedClientError: if problems encountered when posting to eXist
64        '''
65        logging.debug("Setting up POST Request to eXist")
66        connection = httplib.HTTPConnection(self.hostAndPort)
67
68        # NB, authorisation is required for accessing eXist via this interface
69        headers = {
70            'Host': self.hostAndPort, 
71            'Content-Type': 'application/atom+xml',
72            'Authorization': 'Basic %s' %self.auth
73                   }
74       
75        connection.request("POST", '/exist/atom/edit' + collection, 
76                           atomXMLString, headers)
77        logging.debug("Get response...")
78        response = connection.getresponse()
79        logging.debug("Response retrieved")
80
81        if response.status == 204:
82            logging.info("No content returned => new feed set up")
83        elif response.status == 201:
84            logging.info("Successfully posted atom to eXist feed")
85        elif response.status == 401 and response.reason.find('+already+exists') > -1:
86            logging.info("Atom already exists")
87        else:
88            logging.debug("Response error:\n\r %s" %response.read())
89            raise FeedClientError("Error encountered: '%s'" %response.reason)
90
91
92    def setupBasicFeeds(self):
93        '''
94        Set up the standard feeds expected of the eXist DB
95        - nb, provider feeds are set up when creating new atoms - when
96        their providers don't already have feeds available
97        '''
98        self.createAtomFeed(dc.ATOM_COLLECTION_PATH,
99                            self.TOPLEVEL_ATOM_FEED_TITLE)
100        self.VTD = VTD()
101        self.deName = VTD.TERM_DATA[VTD.DE_TERM].title
102        for type in [dc.PUBLISHED_COLLECTION_PATH, dc.SMALL_P_PUBLISHED_COLLECTION_PATH]:
103            self.createAtomFeed(dc.ATOM_COLLECTION_PATH + type + dc.DE_COLLECTION_PATH,
104                                self.ENTITYLEVEL_ATOM_FEED_TITLE %VTD.TERM_DATA[VTD.DE_TERM].title),
105            self.createAtomFeed(dc.ATOM_COLLECTION_PATH + type + dc.DEPLOYMENT_COLLECTION_PATH,
106                                self.ENTITYLEVEL_ATOM_FEED_TITLE %VTD.TERM_DATA[VTD.DEPLOYMENT_TERM].title),
107            self.createAtomFeed(dc.ATOM_COLLECTION_PATH + type + dc.DEPLOYMENTS_COLLECTION_PATH,
108                                self.DEPLOYMENTS_ATOM_FEED_TITLE),
109            self.createAtomFeed(dc.ATOM_COLLECTION_PATH + type + dc.GRANULE_COLLECTION_PATH,
110                                self.ENTITYLEVEL_ATOM_FEED_TITLE %VTD.TERM_DATA[VTD.GRANULE_TERM].title)
111
112        self.createAtomFeed(dc.DIF_COLLECTION_PATH,
113                            self.TOPLEVEL_DIF_FEED_TITLE)
114
115        self.createAtomFeed(dc.CORRECTIONS_COLLECTION_PATH,
116                            self.CORRECTIONS_FEED_TITLE)
117
118
119    def addAtomToFeeds(self, atom):
120        '''
121        Add an entry to the different feeds associated with the specified atom
122        @param atom: an Atom object which should be linked to as an entry in the
123        eXist feeds
124        '''
125        # add to top level feed
126        self.createAtomFeedEntry(dc.ATOM_COLLECTION_PATH, atom)
127        # add to entity level feed
128        self.createAtomFeedEntry(atom.getDefaultEntityCollectionPath(), atom)
129       
130        # add to provider level feed
131        self.createAtomFeedEntry(dc.PROVIDER_FEED_PATH + atom.ME.providerID + '/', atom)
132       
133        # add to DIF feeds - if appropriate
134        if atom.isDE():
135            self.createAtomFeedEntry(dc.DIF_COLLECTION_PATH, atom, isDIFRecord=True)
136            self.createAtomFeedEntry(dc.DIF_COLLECTION_PATH + atom.ME.providerID + '/', 
137                                     atom, isDIFRecord=True)
138       
139
140    def createAtomFeed(self, collection, title):
141        '''
142        Set the specified collection up as an atom feed
143        @param collection: eXist collection to set up as an atom feed
144        @param title: title to give the feed
145        '''
146        feedXML = self.FEED_STRING %(title)
147        logging.info("Setting up %s as an atom feed" %collection)
148        self.__postEditAtomFeed(collection, feedXML)
149        logging.info("%s successfully set up as an atom feed" %collection)
150
151
152    def getAtomFeed(self, collection):
153        '''
154        Retrieve the contents of the feed for the specified eXist collection
155        - in a feed parser object
156        @param collection: eXist collection to retrieve the feed from
157        @return feed: feed in a feedparser object
158        '''
159        logging.info("Retrieving content of atom feed for collection, '%s'" %collection)
160        feed = feedparser.parse('http://%s/exist/atom/content/%s' \
161                                %(self.hostAndPort, collection))
162       
163        # check we've got something valid back
164        if feed.bozo:
165            errorMessage = "%s: '%s'" %(self.FEED_ERROR_MSG, feed.bozo_exception)
166            raise FeedClientError(errorMessage)
167       
168        return feed
169       
170
171    def createAtomFeedEntry(self, collection, atom, isDIFRecord=False):
172        '''
173        Add an entry to the specified collection (which should already have a
174        feed set up on it) - pointing to the specified atom link
175        @param collection: eXist collection with the atom feed to add the entry to
176        @param atom: Atom to be referenced in the new entry
177        @keyword isDIFRecord: if True, the entry contents are adjusted to
178        point to the DIF doc rather than the atom doc.  Default False.
179        '''
180        titleString = '%s Atom' %atom.atomTypeName
181        browseURL = atom.atomBrowseURL
182        if isDIFRecord:
183            titleString = self.DIF_ENTRY_TITLE
184            browseURL = browseURL.replace('__%s__' %ndgObject.ATOM_DOC_TYPE, 
185                                          '__%s__' %ndgObject.BROWSE_DIF_DOC_TYPE)
186           
187        entryXML = '<?xml version="1.0" ?>\
188<entry xmlns="http://www.w3.org/2005/Atom">\
189<title>%s [%s]</title>\
190<summary>%s</summary>\
191<content src="%s" type="application/atom+xml"/>\
192</entry>' %(titleString, atom.title, atom.Summary, browseURL)
193        logging.info("Adding link to atom (%s) as entry in atom feed, %s" \
194                     %(browseURL, collection))
195        self.__postEditAtomFeed(collection, entryXML)
196        logging.info("Successfully added new feed entry")
197       
198
199    def createCorrectionsFeedEntry(self, title, summary):
200        '''
201        Add an entry to the corrections collection (which should already have a
202        feed set up on it) - with details of the corrections data submitted by
203        users of the atom editor
204        @param title: title to give the feed entry
205        @param body: content to add to the summary element
206        '''
207        entryXML = '<?xml version="1.0" ?>\
208<entry xmlns="http://www.w3.org/2005/Atom">\
209<title>%s</title>\
210<content>%s</content>\
211</entry>' %(title, summary)
212        logging.info("Adding corrections data to atom feed")
213        self.__postEditAtomFeed(dc.CORRECTIONS_COLLECTION_PATH, entryXML)
214        logging.info("Successfully added new feed entry")
Note: See TracBrowser for help on using the repository browser.