From c9662b1e10d011bb23f0c25906a47d69961a37c8 Mon Sep 17 00:00:00 2001 From: Alexandre Boeglin <alex@nexedi.com> Date: Thu, 22 Nov 2007 17:47:28 +0000 Subject: [PATCH] * _isTreeDelivered method moved from Rule to AppliedRule and SimulationMovement * added transactional variable based cache to _isTreeDelivered * activate and flush _isTreeDelivered cache in SimulationMovement.expand and AppliedRule.expand (so that it's only active when calling expand, and flushed afterwards, to prevent issues if we call expand in the same transaction as a builder) * modified SimulationMovement.expand behaviour, so that it removes applied rules that no longer test(), and add new ones, if no movment in the applied rule sub tree is linked to a delivery git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@17756 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/Document/AppliedRule.py | 49 ++++++++ product/ERP5/Document/DeliveryRule.py | 4 +- product/ERP5/Document/OrderRule.py | 2 +- product/ERP5/Document/Rule.py | 17 +-- product/ERP5/Document/SimulationMovement.py | 119 +++++++++++++++----- 5 files changed, 142 insertions(+), 49 deletions(-) diff --git a/product/ERP5/Document/AppliedRule.py b/product/ERP5/Document/AppliedRule.py index 202d04e8db..6cf7ee7bfc 100644 --- a/product/ERP5/Document/AppliedRule.py +++ b/product/ERP5/Document/AppliedRule.py @@ -31,9 +31,13 @@ from Products.CMFCore.utils import getToolByName from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.PsycoWrapper import psyco +from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from zLOG import LOG +TREE_DELIVERED_CACHE_KEY = 'AppliedRule._isTreeDelivered_cache' +TREE_DELIVERED_CACHE_ENABLED = 'TREE_DELIVERED_CACHE_ENABLED' + class AppliedRule(XMLObject): """ An applied rule holds a list of simulation movements. @@ -89,6 +93,14 @@ class AppliedRule(XMLObject): An applied rule can be expanded only if its parent movement is expanded. """ + tv = getTransactionalVariable(self) + cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {}) + cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0) + + # enable cache + if not cache_enabled: + cache[TREE_DELIVERED_CACHE_ENABLED] = 1 + rule = self.getSpecialiseValue() if rule is not None: if self.isRootAppliedRule(): @@ -102,6 +114,13 @@ class AppliedRule(XMLObject): # "recursiveImmediateReindexObject"]).\ # notifySimulationChange(rule._v_notify_dict) + # disable and clear cache + if not cache_enabled: + try: + del tv[TREE_DELIVERED_CACHE_KEY] + except KeyError: + pass + security.declareProtected(Permissions.ModifyPortalContent, 'solve') def solve(self, solution_list): """ @@ -193,3 +212,33 @@ class AppliedRule(XMLObject): delivery_url) else: delivery_value.notifySimulationChange() + + def _isTreeDelivered(self): + """ + Checks if submovements of this applied rule (going down the complete + simulation tree) have a delivery relation. + Returns True if at least one is delivered, False if none of them are. + + see SimulationMovement._isTreeDelivered + """ + tv = getTransactionalVariable(self) + cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {}) + cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0) + + def getTreeDelivered(applied_rule): + for movement in applied_rule.objectValues(): + if movement._isTreeDelivered(): + return True + return False + + rule_key = self.getRelativeUrl() + if cache_enabled: + try: + return cache[rule_key] + except: + result = getTreeDelivered(self) + cache[rule_key] = result + return result + else: + return getTreeDelivered(self) + diff --git a/product/ERP5/Document/DeliveryRule.py b/product/ERP5/Document/DeliveryRule.py index dc47a06f22..156c2bdb13 100644 --- a/product/ERP5/Document/DeliveryRule.py +++ b/product/ERP5/Document/DeliveryRule.py @@ -85,7 +85,7 @@ class DeliveryRule(Rule): (delivery.getPortalDraftOrderStateList() + \ delivery.getPortalPlannedOrderStateList()): movement_delivery = movement.getDeliveryValue() - if not self._isTreeDelivered([movement], ignore_first=1) and \ + if not movement._isTreeDelivered(ignore_first=1) and \ movement_delivery not in delivery_movement_list: applied_rule._delObject(movement.getId()) else: @@ -103,7 +103,7 @@ class DeliveryRule(Rule): # We are on a line new_id = deliv_mvt.getId() else: - # Weare on a cell + # We are on a cell new_id = "%s_%s" % (deliv_mvt.getParentId(), deliv_mvt.getId()) # Generate the simulation deliv_mvt # XXX Hardcoded value diff --git a/product/ERP5/Document/OrderRule.py b/product/ERP5/Document/OrderRule.py index 0700000439..fefa9e0ad2 100644 --- a/product/ERP5/Document/OrderRule.py +++ b/product/ERP5/Document/OrderRule.py @@ -86,7 +86,7 @@ class OrderRule(DeliveryRule): order.getPortalReservedInventoryStateList() and not movement.getLastExpandSimulationState() in order.getPortalCurrentInventoryStateList()) and \ - not self._isTreeDelivered([movement]): + not movement._isTreeDelivered(): movement_order = movement.getOrderValue() if movement_order in order_movement_list: diff --git a/product/ERP5/Document/Rule.py b/product/ERP5/Document/Rule.py index 6a21ab0e45..dffabc5561 100644 --- a/product/ERP5/Document/Rule.py +++ b/product/ERP5/Document/Rule.py @@ -220,21 +220,6 @@ class Rule(Predicate, XMLObject): return 1 #### Helpers - def _isTreeDelivered(self, movement_list, ignore_first=0): - """ - returns 1 if the movement or any of its child is linked to a delivery - """ - child_movement_list = [] - for movement in movement_list: - if not ignore_first and len(movement.getDeliveryList()) > 0: - return 1 - else: - for applied_rule in movement.objectValues(): - child_movement_list = applied_rule.objectValues() - if len(child_movement_list) == 0: - return 0 - return self._isTreeDelivered(child_movement_list) - def _getCurrentMovementList(self, applied_rule, **kw): """ Returns the list of current children of the applied rule, sorted in 3 @@ -263,7 +248,7 @@ class Rule(Predicate, XMLObject): if movement.isFrozen(): immutable_movement_list.append(movement) else: - if self._isTreeDelivered([movement]): + if movement._isTreeDelivered(): mutable_movement_list.append(movement) else: deletable_movement_list.append(movement) diff --git a/product/ERP5/Document/SimulationMovement.py b/product/ERP5/Document/SimulationMovement.py index a851c8647c..2c1c9ccd65 100644 --- a/product/ERP5/Document/SimulationMovement.py +++ b/product/ERP5/Document/SimulationMovement.py @@ -31,6 +31,7 @@ from AccessControl import ClassSecurityInfo from Products.CMFCore.utils import getToolByName from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface +from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5.Document.Movement import Movement @@ -38,6 +39,8 @@ from zLOG import LOG from Acquisition import aq_base +from AppliedRule import TREE_DELIVERED_CACHE_KEY, TREE_DELIVERED_CACHE_ENABLED + # XXX Do we need to create groups ? (ie. confirm group include confirmed, getting_ready and ready parent_to_movement_simulation_state = { @@ -209,36 +212,57 @@ class SimulationMovement(Movement): security.declareProtected(Permissions.ModifyPortalContent, 'expand') def expand(self, force=0, **kw): """ - Parses all existing applied rules and make sure they apply. - Checks other possible rules and starts expansion process - (instanciates rule and calls expand on rule) - - Only movements which applied rule parent is expanded can - be expanded. - """ - # XXX Default behaviour is not to expand if it has already been - # expanded, but some rules are configuration rules and need to be - # reexpanded each time, because the rule apply only if predicates - # are true, then this kind of rule must always be tested. Currently, - # we know that invoicing rule acts like this, and that it comes after - # invoice or invoicing_rule, so we if we come from invoince rule or - # invoicing rule, we always expand regardless of the causality state. - if ((self.getParentValue().getSpecialiseReference() not in - ('default_invoicing_rule', 'default_invoice_rule') - and self.getCausalityState() == 'expanded' ) or \ - len(self.objectIds()) != 0): - # Reexpand - for my_applied_rule in self.objectValues(): - my_applied_rule.expand(force=force,**kw) - else: - portal_rules = getToolByName(self, 'portal_rules') - # Parse each rule and test if it applies - for rule in portal_rules.searchRuleList(self): - rule.constructNewAppliedRule(self, **kw) - for my_applied_rule in self.objectValues() : - my_applied_rule.expand(force=force,**kw) - # Set to expanded - self.setCausalityState('expanded') + Checks all existing applied rules and make sure they still apply. + Checks for other possible rules and starts expansion process (instanciates + applied rules and calls expand on them). + + First get all applicable rules, + then, delete all applied rules that no longer match and are not linked to + a delivery, + finally, apply new rules if no rule with the same type is already applied. + """ + portal_rules = getToolByName(self, 'portal_rules') + + tv = getTransactionalVariable(self) + cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {}) + cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0) + + # enable cache + if not cache_enabled: + cache[TREE_DELIVERED_CACHE_ENABLED] = 1 + + applied_rule_dict = {} + applicable_rule_dict = {} + for rule in portal_rules.searchRuleList(self, sort_on='version', + sort_order='descending'): + ref = rule.getReference() + if ref and ref not in applicable_rule_dict.iterkeys(): + applicable_rule_dict[ref] = rule + + for applied_rule in self.objectValues(): + rule = applied_rule.getSpecialiseValue() + if not applied_rule._isTreeDelivered() and not rule.test(self): + self._delObject(applied_rule.getId()) + else: + applied_rule_dict[rule.getPortalType()] = applied_rule + + for rule in applicable_rule_dict.itervalues(): + rule_type = rule.getPortalType() + if rule_type not in applied_rule_dict.iterkeys(): + applied_rule = rule.constructNewAppliedRule(self, **kw) + applied_rule_dict[rule_type] = applied_rule + + self.setCausalityState('expanded') + # expand + for applied_rule in applied_rule_dict.itervalues(): + applied_rule.expand(force=force, **kw) + + # disable and clear cache + if not cache_enabled: + try: + del tv[TREE_DELIVERED_CACHE_KEY] + except KeyError: + pass security.declareProtected(Permissions.ModifyPortalContent, 'diverge') def diverge(self): @@ -480,3 +504,38 @@ class SimulationMovement(Movement): # 'recursiveImmediateReindexObject'])) # activity.edit() + def _isTreeDelivered(self, ignore_first=0): + """ + checks if subapplied rules of this movement (going down the complete + simulation tree) have a child with a delivery relation. + Returns True if at least one is delivered, False if none of them are. + + see AppliedRule._isTreeDelivered + """ + tv = getTransactionalVariable(self) + cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {}) + cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0) + + def getTreeDelivered(movement, ignore_first=0): + if ignore_first: + if len(movement.getDeliveryList()) > 0: + return True + for applied_rule in movement.objectValues(): + if applied_rule._isTreeDelivered(): + return True + return False + + if ignore_first: + rule_key = (self.getRelativeUrl(), 1) + else: + rule_key = self.getRelativeUrl() + if cache_enabled: + try: + return cache[rule_key] + except: + result = getTreeDelivered(self, ignore_first=ignore_first) + cache[rule_key] = result + return result + else: + return getTreeDelivered(self, ignore_first=ignore_first) + -- 2.30.9