#-- # Copyright (c) 2006-2009, Nicolas Modryzk and John Mettraux, OpenWFE.org # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # Made in Japan. #++ require 'rufus/lru' require 'openwfe/service' require 'openwfe/flowexpressionid' require 'openwfe/rudefinitions' module OpenWFE # # This module contains the observe_expool method which binds the # storage to the expression pool. # It also features a to_s method for the expression storages including # it. # module ExpressionStorageBase def observe_expool return unless get_expression_pool get_expression_pool.add_observer(:update) do |channel, fei, fe| self[fei] = fe end get_expression_pool.add_observer(:remove) do |channel, fei| self.delete(fei) end end # # a human readable representation of the content of the expression # storage. # # Warning : this will display the content of the real storage, # (especially when called against a cache). # def to_s s = "\n\n==== #{self.class} ====" find_expressions.each do |fexp| s << "\n" if fexp.kind_of?(RawExpression) s << "*raw" else s << " " end s << fexp.fei.to_s end s << "\n==== . ====\n" s end # # This method is used by the various implementations of # find_expressions() # def does_match? (options, fexp_or_fei) wfid = options[:wfid] wfid_prefix = options[:wfid_prefix] parent_wfid = options[:parent_wfid] wfname = options[:wfname] wfrevision = options[:wfrevision] ic = options[:include_classes] ec = options[:exclude_classes] ic = Array(ic) if ic ec = Array(ec) if ec cs = options[:consider_subprocesses] ap = options[:applied] fexp, fei = if fexp_or_fei.is_a?(FlowExpressionId) [ nil, fexp_or_fei ] else [ fexp_or_fei, fexp_or_fei.fei ] end # # let's make it verbose... if fexp return false if (ap == true and not fexp.apply_time) return false if (ap == false and fexp.apply_time) return false unless class_accepted?(fexp, ic, ec) end return false \ if wfname and fei.wfname != wfname return false \ if wfrevision and fei.wfrevision != wfrevision return false \ if cs and fei.sub_instance_id != '' return false \ if wfid and fei.wfid != wfid return false \ if wfid_prefix and not fei.wfid.match("^#{wfid_prefix}") return false \ if parent_wfid and not fei.parent_wfid == parent_wfid true end # # Returns true if the given expression is in the list of included # classes or false if it's in the list of excluded classes... # # include_classes has precedence of exclude_classes. # def class_accepted? (fexp, include_classes, exclude_classes) return false if include_classes and (not include_classes.find do |klazz| fexp.is_a?(klazz) end) return false if exclude_classes and exclude_classes.find do |klazz| fexp.is_a?(klazz) end true end end # # This cache uses a LruHash (Least Recently Used) to store expressions. # If an expression is not cached, the 'real storage' is consulted. # The real storage is supposed to be the service named # "expressionStorage.1" # class CacheExpressionStorage include ServiceMixin include OwfeServiceLocator include ExpressionStorageBase # # under 20 stored expressions, the unit tests for the # CachedFilePersistedEngine do fail because the persistent storage # behind the cache hasn't the time to flush its work queue. # a min size limit has been set to 77. # MIN_SIZE = 77 DEFAULT_SIZE = 5000 def initialize (service_name, application_context) super() service_init(service_name, application_context) size = @application_context[:expression_cache_size] || DEFAULT_SIZE size = MIN_SIZE unless size > MIN_SIZE linfo { "new() size is #{size}" } @cache = LruHash.new(size) observe_expool end def [] (fei) #ldebug { "[] size is #{@cache.size}" } #ldebug { "[] (sz #{@cache.size}) for #{fei.to_debug_s}" } fe = @cache[fei.short_hash] return fe if fe #ldebug { "[] (reload) for #{fei.to_debug_s}" } fe = get_real_storage[fei] unless fe #ldebug { "[] (reload) miss for #{fei.to_debug_s}" } return nil end @cache[fei.short_hash] = fe end def []= (fei, fe) #ldebug { "[]= caching #{fei}" } @cache[fei.short_hash] = fe end def delete (fei) @cache.delete(fei.short_hash) end # # returns the count of expressions currently cached here # def length @cache.length end alias :size :length def clear @cache.clear end alias :purge :clear # # This implementations of find_expressions() immediately passes # the call to the underlying real storage. # def find_expressions (options={}) options[:cache] = self get_real_storage.find_expressions(options) end # # Attempts at fetching the root expression of a given process # instance. # def fetch_root (wfid) @cache.values.find { |fexp| fexp.fei.wfid == wfid and fexp.is_a?(DefineExpression) } || get_real_storage.fetch_root(wfid) end # # Returns the expression corresponding to the fei if cached. # Does not lookup in the underlying "real" storage (will therefore # return nil if the expression is not cached. # def fetch (fei) @cache[fei.short_hash] end protected # # Returns the "real storage" i.e. the storage that does the real # persistence behind this "cache storage". # def get_real_storage @application_context[:s_expression_storage__1] end end # # [memory consuming] in-memory storage. # No memory limit, puts everything in a Hash # # USE ONLY FOR TESTS # class InMemoryExpressionStorage < Hash include ServiceMixin include OwfeServiceLocator include ExpressionStorageBase def initialize (service_name, application_context) service_init(service_name, application_context) observe_expool end alias :purge :clear #-- #def [] (k) # super(k.short_hash) #end #def []= (k, v) # super(k.short_hash, v) #end #def delete (k) # super(k.short_hash) #end #++ # # Finds expressions matching the given criteria (returns a list # of expressions). # # This methods is called by the expression pool, it's thus not # very "public" (not used directly by integrators, who should # just focus on the methods provided by the Engine). # # :wfid :: # will list only one process, # :wfid => '20071208-gipijiwozo' # :parent_wfid :: # will list only one process, and its subprocesses, # :parent_wfid => '20071208-gipijiwozo' # :consider_subprocesses :: # if true, "process-definition" expressions # of subprocesses will be returned as well. # :wfid_prefix :: # allows your to query for specific workflow instance # id prefixes. for example : # :wfid_prefix => "200712" # for the processes started in December. # :include_classes :: # excepts a class or an array of classes, only instances of these # classes will be returned. Parent classes or mixins can be # given. # :includes_classes => OpenWFE::SequenceExpression # :exclude_classes :: # works as expected. # :wfname :: # will return only the expressions who belongs to the given # workflow [name]. # :wfrevision :: # usued in conjuction with :wfname, returns only the expressions # with a given workflow revision. # :applied :: # if this option is set to true, will only return the expressions # that have been applied (exp.apply_time != nil). # def find_expressions (options={}) values.find_all { |fexp| does_match?(options, fexp) } end # # Attempts at fetching the root expression of a given process # instance. # def fetch_root (wfid) find_expressions(:wfid => wfid, :include_classes => DefineExpression)[0] end end end