clarification
[Plinn.git] / catalog.py
index 382fc57..69bb701 100644 (file)
@@ -5,94 +5,134 @@ from Products.CMFCore.interfaces import IIndexableObject
 from Products.CMFCore.CatalogTool import CatalogTool as BaseCatalogTool
 from Products.CMFCore.CatalogTool import IndexableObjectWrapper
 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
-from Products.CMFCore.permissions import ModifyPortalContent
+from Products.CMFCore.permissions import ModifyPortalContent, ManagePortal
 from zope.component import queryMultiAdapter
 from Products.ZCatalog.Catalog import Catalog
 import transaction
 from solr import *
 
+# imports for Catalog class
+from Products.PluginIndexes.interfaces import ILimitedResultIndex
+from Products.ZCatalog.Lazy import LazyMap, LazyCat, LazyValues
+from BTrees.IIBTree import intersection, IISet
+from BTrees.IIBTree import weightedIntersection
+import warnings
+
+_VOLATILE_SOLR_NAME = '_v_solrConnection'
+
 class SolrTransactionHook :
     ''' commit solr couplé sur le commit de la ZODB '''
-    def __init__(self, connection) :
-        self.connection = connection
+    def __init__(self, context, con) :
+        self.context = context
+        self.con = con
     
     def __call__(self, status) :
         if status :
-            self.connection.commit()
-            self.connection.close()
+            self.con.commit()
+            self.con.close()
         else :
-            self.connection.close()
+            self.con.close()
+        try :
+            delattr(self.context, _VOLATILE_SOLR_NAME)
+        except AttributeError :
+            pass
 
 class CatalogTool(BaseCatalogTool) :
-    meta_type = 'Legivoc Catalog'
+    meta_type = 'Plinn Catalog'
     security = ClassSecurityInfo()
     manage_options = (BaseCatalogTool.manage_options[:5] +
                       ({'label' : 'Solr', 'action' : 'manage_solr'},) +
                       BaseCatalogTool.manage_options[5:])
-    manage_solr = PageTemplateFile('www/manage_solr', globals())
+    manage_solr = PageTemplateFile('www/manage_solr.pt', globals(), __name__='manage_solr')
+    
     
     
     def __init__(self, idxs=[]) :
         super(CatalogTool, self).__init__()
-        self._catalog = DelegatedCatalog()
+        self._catalog = DelegatedCatalog(self)
         self.solr_url = 'http://localhost:8983/solr'
         self.delegatedIndexes = ('Title', 'Description', 'SearchableText')
     
+    security.declarePublic('getDelegatedIndexes')
+    def getDelegatedIndexes(self) :
+        """ read the method name """
+        return self.delegatedIndexes
+    
+    security.declareProtected(ManagePortal, 'setDelegatedIndexes')
+    def setDelegatedIndexes(self, indexes, REQUEST=None) :
+        """setDelegatedIndexes documentation"""
+        self.delegatedIndexes = tuple([i.strip() for i in indexes if i.strip()])
+        if REQUEST :
+            REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_solr?manage_tabs_message=Saved changes.')
+    
+    def _getSolrConnection(self) :
+        if not hasattr(self, _VOLATILE_SOLR_NAME) :
+            con = SolrConnection(self.solr_url)
+            setattr(self, _VOLATILE_SOLR_NAME, con)
+            txn = transaction.get()
+            txn.addAfterCommitHook(SolrTransactionHook(self, con))
+        return getattr(self, _VOLATILE_SOLR_NAME)
+    
     security.declarePrivate('solrAdd')
-    def solrAdd(self, object, idxs=[], uid=None) :
-        if IIndexableObject.providedBy(object):
-            w = object
-        else:
-            w = queryMultiAdapter( (object, self), IIndexableObject )
-            if w is None:
-                # BBB
-                w = IndexableObjectWrapper(object, self)
-        
-        uid = uid if uid else self.__url(object)
-        idxs = idxs if idxs !=[] else self.delegatedIndexes
+    def solrAdd(self, w, uid, idxs) :
+        idxs = idxs if idxs else self.delegatedIndexes
+        # Filter out delegated indexes
+        idxs = [i for i in idxs if i in self.delegatedIndexes]
         data = {'id' : uid}
         for name in idxs :
             attr = getattr(w, name, '')
             data[name] = attr() if callable(attr) else attr
-        c = SolrConnection(self.solr_url)
+        c = self._getSolrConnection()
         c.add(**data)
-        txn = transaction.get()
-        txn.addAfterCommitHook(SolrTransactionHook(c))
-    
     
     # PortalCatalog api overloads
-    security.declareProtected(ModifyPortalContent, 'indexObject')
-    def indexObject(self, object) :
-        """ Add to catalog and send to Solr """
-        super(CatalogTool, self).indexObject(object)
-        self.solrAdd(object)
-
-    security.declarePrivate('reindexObject')
-    def reindexObject(self, object, idxs=[], update_metadata=1, uid=None):
-        super(CatalogTool, self).reindexObject(object,
-                                               idxs=idxs,
-                                               update_metadata=update_metadata,
-                                               uid=uid)
-        if idxs != []:
+    def catalog_object(self, obj, uid=None, idxs=None, update_metadata=1,
+                       pghandler=None):
+        # Wraps the object with workflow and accessibility
+        # information just before cataloging.
+        if IIndexableObject.providedBy(obj):
+            w = obj
+        else:
+            w = queryMultiAdapter( (obj, self), IIndexableObject )
+            if w is None:
+                # BBB
+                w = IndexableObjectWrapper(obj, self)
+        
+        idxs_ = idxs
+        if idxs:
             # Filter out invalid indexes.
             valid_indexes = self._catalog.indexes.keys()
-            idxs = [i for i in idxs if i in valid_indexes and i in self.delegatedIndexes]
-        else :
-            idxs = self.delegatedIndexes
+            idxs_ = [i for i in idxs if i in valid_indexes]
+        
+        super(CatalogTool, self).catalog_object(w, uid, idxs_, update_metadata, pghandler)
+        self.solrAdd(w, uid, idxs)
+    
+    security.declarePrivate('reindexObject')
+    def reindexObject(self, object, idxs=[], update_metadata=1, uid=None):
+        """Update catalog after object data has changed.
 
-        if idxs :
-            self.solrAdd(object, idxs=idxs, uid=uid)
+        The optional idxs argument is a list of specific indexes
+        to update (all of them by default).
+
+        The update_metadata flag controls whether the object's
+        metadata record is updated as well.
+
+        If a non-None uid is passed, it will be used as the catalog uid
+        for the object instead of its physical path.
+        """
+        if uid is None:
+            uid = self.__url(object)
+
+        self.catalog_object(object, uid, idxs, update_metadata)
     
     security.declarePrivate('unindexObject')
     def unindexObject(self, object):
         """Remove from catalog.
         """
         super(CatalogTool, self).unindexObject(object)
-        c = SolrConnection(self.solr_url)
+        c = self._getSolrConnection()
         url = self.__url(object)
         c.delete(id=url)
-        txn = transaction.get()
-        txn.addAfterCommitHook(SolrTransactionHook(c))
         
 InitializeClass(CatalogTool)
 
@@ -100,6 +140,30 @@ InitializeClass(CatalogTool)
 class DelegatedCatalog(Catalog) :
     '''C'est ici qu'on délègue effectivement à Solr '''
     
+    def __init__(self, zcat, brains=None) :
+        Catalog.__init__(self, brains=brains)
+        self.zcat = zcat
+    
+    def delegateSearch(self, query, plan) :
+        '''
+        retours faux : 
+        None signifie : pas de délégation, il faut continuer à interroger les autres index.
+        IISet() vide : pas de résultat lors de la délégation, on peut arrêter la recherche.
+        '''
+        indexes = set(query.keys()).intersection(set(self.zcat.delegatedIndexes))
+        if not indexes :
+            return None
+        delegatedQuery = {}
+        for i in indexes :
+            delegatedQuery[i] = query.pop(i)
+            try : plan.remove(i)
+            except ValueError : pass
+        c = SolrConnection(self.zcat.solr_url)
+        q =' AND '.join(['%s:"%s"' % item for item in delegatedQuery.items()])
+        resp = c.query(q, fields='id', rows=len(self))
+        c.close()
+        return IISet(filter(None, [self.uids.get(r['id']) for r in resp.results])) 
+    
     def search(self, query, sort_index=None, reverse=0, limit=None, merge=1):
         """Iterate through the indexes, applying the query to each one. If
         merge is true then return a lazy result set (sorted if appropriate)
@@ -129,6 +193,11 @@ class DelegatedCatalog(Catalog) :
         plan = cr.plan()
         if not plan:
             plan = self._sorted_search_indexes(query)
+        
+        # délégation
+        rs = self.delegateSearch(query, plan)
+        if rs is not None and not rs :
+            return LazyCat([])
 
         indexes = self.indexes.keys()
         for i in plan: