lib/openwfe/extras/participants/activeparticipants.rb in ruote-0.9.19 vs lib/openwfe/extras/participants/activeparticipants.rb in ruote-0.9.20

- old
+ new

@@ -1,749 +1,3 @@ -# -#-- -# Copyright (c) 2007-2008, John Mettraux, Tomaso Tosolini OpenWFE.org -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# . Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# . Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# . Neither the name of the "OpenWFE" nor the names of its contributors may be -# used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -#++ -# -# -# "made in Japan" -# -# John Mettraux at openwfe.org -# Tomaso Tosolini -# - -#require 'rubygems' - -#require_gem 'activerecord' -gem 'activerecord'; require 'active_record' - - -require 'openwfe/workitem' -require 'openwfe/flowexpressionid' -require 'openwfe/engine/engine' -require 'openwfe/participants/participant' - - -module OpenWFE -module Extras - - #MUTEX = Mutex.new - - # - # The migration for ActiveParticipant and associated classes. - # - # There are two tables 'workitems' and 'fields'. As its name implies, - # the latter table stores the fields (also called attributes in OpenWFE - # speak) of the workitems. - # - # See Workitem and Field for more details. - # - # For centralization purposes, the migration and the model are located - # in the same source file. It should be quite easy for the Rails hackers - # among you to sort that out for a Rails based usage. - # - class WorkitemTables < ActiveRecord::Migration - - def self.up - - create_table :workitems do |t| - t.column :fei, :string - t.column :wfid, :string - t.column :wf_name, :string - t.column :wf_revision, :string - t.column :participant_name, :string - t.column :store_name, :string - t.column :dispatch_time, :timestamp - t.column :last_modified, :timestamp - - t.column :yattributes, :text - # when using compact_workitems, attributes are stored here - end - add_index :workitems, :fei, :unique => true - # with sqlite3, comment out this :unique => true on :fei :( - add_index :workitems, :wfid - add_index :workitems, :wf_name - add_index :workitems, :wf_revision - add_index :workitems, :participant_name - add_index :workitems, :store_name - - create_table :fields do |t| - t.column :fkey, :string, :null => false - t.column :vclass, :string, :null => false - t.column :svalue, :string - t.column :yvalue, :text - t.column :workitem_id, :integer, :null => false - end - add_index :fields, [ :workitem_id, :fkey ], :unique => true - add_index :fields, :fkey - add_index :fields, :vclass - add_index :fields, :svalue - end - - def self.down - - drop_table :workitems - drop_table :fields - end - end - - # - # Reopening InFlowWorkItem to add a 'db_id' attribute. - # - class OpenWFE::InFlowWorkItem - - attr_accessor :db_id - end - - # - # The ActiveRecord version of an OpenWFEru workitem (InFlowWorkItem). - # - # One can very easily build a worklist based on a participant name via : - # - # wl = OpenWFE::Extras::Workitem.find_all_by_participant_name("toto") - # puts "found #{wl.size} workitems for participant 'toto'" - # - # These workitems are not OpenWFEru workitems directly. But the conversion - # is pretty easy. - # Note that you probaly won't need to do the conversion by yourself, - # except for certain advanced scenarii. - # - # awi = OpenWFE::Extras::Workitem.find_by_participant_name("toto") - # # - # # returns the first workitem in the database whose participant - # # name is 'toto'. - # - # owi = awi.as_owfe_workitem - # # - # # Now we have a copy of the reference as a OpenWFEru - # # InFlowWorkItem instance. - # - # awi = OpenWFE::Extras::Workitem.from_owfe_workitem(owi) - # # - # # turns an OpenWFEru InFlowWorkItem instance into an - # # 'active workitem'. - # - class Workitem < ActiveRecord::Base - - has_many :fields, :dependent => :destroy - - serialize :yattributes - - - # - # Returns the flow expression id of this work (its unique OpenWFEru - # identifier) as a FlowExpressionId instance. - # (within the Workitem it's just stored as a String). - # - def full_fei - - OpenWFE::FlowExpressionId.from_s(fei) - end - - # - # Making sure last_modified is set to Time.now before each save. - # - def before_save - - touch - end - - # - # Generates a (new) Workitem from an OpenWFEru InFlowWorkItem instance. - # - # This is a 'static' method : - # - # awi = OpenWFE::Extras::Workitem.from_owfe_workitem(wi) - # - # (This method saves the 'ActiveWorkitem'). - # - def Workitem.from_owfe_workitem (wi, store_name=nil) - - i = Workitem.new - i.fei = wi.fei.to_s - i.wfid = wi.fei.wfid - i.wf_name = wi.fei.workflow_definition_name - i.wf_revision = wi.fei.workflow_definition_revision - i.participant_name = wi.participant_name - i.dispatch_time = wi.dispatch_time - i.last_modified = nil - - i.store_name = store_name - - i.save! - # save workitem before adding any field - # making sure it has an id... - - i = Workitem.find_by_fei(wi.fei.to_s) if i.id == 0 - # sometimes, the saved workitem id wasn't updated, was remaining at 0 - # thus finding if necessary... - - #i.fields.delete_all - # why do I need that ??? fields were getting recycled... - - # This is a field set by the active participant immediately - # before calling this method. - # the default behavior is "use field method" - - if wi.attributes["compact_workitems"] - - wi.attributes.delete("compact_workitems") - i.yattributes = wi.attributes - - else - - i.yattributes = nil - - wi.attributes.each do |k, v| - i.fields << Field.new_field(k, v) - end - end - - i.save! - # making sure to throw an exception in case of trouble - # - # damn, insert then update :( - - i - end - - # - # Turns the densha Workitem into an OpenWFEru InFlowWorkItem. - # - def as_owfe_workitem - - wi = OpenWFE::InFlowWorkItem.new - - wi.fei = full_fei - wi.participant_name = participant_name - wi.attributes = fields_hash - - wi.dispatch_time = dispatch_time - wi.last_modified = last_modified - - wi.db_id = self.id - - wi - end - - # - # Returns a hash version of the 'fields' of this workitem. - # - # (Each time this method is called, it returns a new hash). - # - def fields_hash - - return self.yattributes if self.yattributes - - fields.inject({}) do |r, f| - r[f.fkey] = f.value - r - end - end - - # - # Replaces the current fields of this workitem with the given hash. - # - # This method modifies the content of the db. - # - def replace_fields (fhash) - - if self.yattributes - - self.yattributes = fhash - - else - - fields.delete_all - - fhash.each do |k, v| - fields << Field.new_field(k, v) - end - end - - #f = Field.new_field("___map_type", "smap") - # - # an old trick for backward compatibility with OpenWFEja - - save! - # making sure to throw an exception in case of trouble - end - - # - # Returns the Field instance with the given key. This method accept - # symbols as well as strings as its parameter. - # - # wi.field("customer_name") - # wi.field :customer_name - # - def field (key) - - if self.yattributes - return self.yattributes[key.to_s] - end - - fields.find_by_fkey key.to_s - end - - # - # A shortcut method, replies to the workflow engine and removes self - # from the database. - # Handy for people who don't want to play with an ActiveParticipant - # instance when just consuming workitems (that an active participant - # pushed in the database). - # - def reply (engine) - - engine.reply self.as_owfe_workitem - self.destroy - end - - alias :forward :reply - alias :proceed :reply - - # - # Simply sets the 'last_modified' field to now. - # (Doesn't save the workitem though). - # - def touch - - self.last_modified = Time.now - end - - # - # Opening engine to update its reply method to accept these - # active record workitems. - # - class OpenWFE::Engine - - alias :oldreply :reply - - def reply (workitem) - - if workitem.is_a?(Workitem) - - oldreply(workitem.as_owfe_workitem) - workitem.destroy - else - - oldreply(workitem) - end - end - - alias :forward :reply - alias :proceed :reply - end - - # - # Returns all the workitems belonging to the stores listed - # in the parameter storename_list. - # The result is a Hash whose keys are the store names and whose - # values are list of workitems. - # - def self.find_in_stores (storename_list) - - workitems = find_all_by_store_name(storename_list) - - result = {} - - workitems.each do |wi| - (result[wi.store_name] ||= []) << wi - end - - result - end - - # - # A kind of 'google search' among workitems - # - # == Note - # - # when this is used on compact_workitems, it will not be able to search - # info within the fields, because they aren't used by this kind of - # workitems. In this case the search will be limited to participant_name - # - def self.search (search_string, storename_list=nil) - - #t = OpenWFE::Timer.new - - storename_list = Array(storename_list) if storename_list - - # participant_name - - result = find( - :all, - :conditions => conditions( - "participant_name", search_string, storename_list), - :order => "participant_name") - # :limit => 10) - - ids = result.collect { |wi| wi.id } - - # search in fields - - fields = Field.search search_string, storename_list - merge_search_results ids, result, fields - - #puts "... took #{t.duration} ms" - - # over. - - result - end - - # - # Not really about 'just launched', but rather about finding the first - # workitem for a given process instance (wfid) and a participant. - # It deserves its own method because the workitem could be in a - # subprocess, thus escaping the vanilla find_by_wfid_and_participant() - # - def self.find_just_launched (wfid, participant_name) - - find( - :first, - :conditions => [ - "wfid LIKE ? AND participant_name = ?", - "#{wfid}%", - participant_name ]) - end - - protected - - # - # builds the condition (the WHERE clause) for the - # search. - # - def self.conditions (keyname, search_string, storename_list) - - cs = [ "#{keyname} LIKE ?", search_string ] - - if storename_list - - cs[0] = "#{cs[0]} AND workitems.store_name IN (?)" - cs << storename_list - end - - cs - end - - def self.merge_search_results (ids, wis, new_wis) - - return if new_wis.size < 1 - - new_wis.each do |wi| - wi = wi.workitem if wi.kind_of?(Field) - next if ids.include? wi.id - ids << wi.id - wis << wi - end - end - end - - # - # A workaround is in place for some classes when then have to get - # serialized. The names of thoses classes are listed in this array. - # - SPECIAL_FIELD_CLASSES = [ 'Time', 'Date', 'DateTime' ] - - # - # A Field (Attribute) of a Workitem. - # - class Field < ActiveRecord::Base - - belongs_to :workitem - serialize :yvalue - - # - # A quick method for doing - # - # f = Field.new - # f.key = key - # f.value = value - # - # One can then quickly add fields to an [active] workitem via : - # - # wi.fields << Field.new_field("toto", "b") - # - # This method does not save the new Field. - # - def self.new_field (key, value) - - f = Field.new - f.fkey = key - f.vclass = value.class.to_s - f.value = value - f - end - - def value= (v) - - limit = connection.native_database_types[:string][:limit] - - if v.is_a?(String) and v.length <= limit - - self.svalue = v - - elsif SPECIAL_FIELD_CLASSES.include?(v.class.to_s) - - self.svalue = v.to_yaml - - else - - self.yvalue = v - end - end - - def value - - return YAML.load(self.svalue) \ - if SPECIAL_FIELD_CLASSES.include?(self.vclass) - - self.svalue || self.yvalue - end - - # - # Will return all the fields that contain the given text. - # - # Looks in svalue and fkey. Looks as well in yvalue if it contains - # a string. - # - # This method is used by Workitem.search() - # - def self.search (text, storename_list=nil) - - cs = build_search_conditions(text) - - if storename_list - - cs[0] = "(#{cs[0]}) AND workitems.store_name IN (?)" - cs << storename_list - end - - find :all, :conditions => cs, :include => :workitem - end - - protected - - # - # The search operates on the content of these columns - # - FIELDS_TO_SEARCH = %w{ svalue fkey yvalue } - - # - # Builds the condition array for a pseudo text search - # - def self.build_search_conditions (text) - - has_percent = (text.index("%") != nil) - - conds = [] - - conds << FIELDS_TO_SEARCH.collect { |key| - - count = has_percent ? 1 : 4 - - s = ([ "#{key} LIKE ?" ] * count).join(" OR ") - - s = "(vclass = ? AND (#{s}))" if key == 'yvalue' - - s - }.join(" OR ") - - FIELDS_TO_SEARCH.each do |key| - - conds << 'String' if key == 'yvalue' - - conds << text - - unless has_percent - conds << "% #{text} %" - conds << "% #{text}" - conds << "#{text} %" - end - end - - conds - end - end - - - # - # A basic 'ActiveParticipant'. - # A store participant whose store is a set of ActiveRecord tables. - # - # Sample usage : - # - # class MyDefinition < OpenWFE::ProcessDefinition - # sequence do - # active0 - # active1 - # end - # end - # - # def play_with_the_engine - # - # engine = OpenWFE::Engine.new - # - # engine.register_participant( - # :active0, OpenWFE::Extras::ActiveParticipant) - # engine.register_participant( - # :active1, OpenWFE::Extras::ActiveParticipant) - # - # li = OpenWFE::LaunchItem.new(MyDefinition) - # li.customer_name = 'toto' - # engine.launch li - # - # sleep 0.500 - # # give some slack to the engine, it's asynchronous after all - # - # wi = OpenWFE::Extras::Workitem.find_by_participant_name("active0") - # - # # ... - # end - # - # == Compact workitems - # - # It is possible to save all the workitem data into a single table, - # the workitems table, without - # splitting info between workitems and fields tables. - # - # You can configure the "compact_workitems" behavior by adding to the - # previous lines: - # - # active0 = engine.register_participant( - # :active0, OpenWFE::Extras::ActiveParticipant) - # - # active0.compact_workitems = true - # - # This behaviour is determined participant per participant, it's ok to - # have a participant instance that compacts will there is another that - # doesn't compact. - # - class ActiveParticipant - include OpenWFE::LocalParticipant - - # - # when compact_workitems is set to true, the attributes of a workitem - # are stored in the yattributes column (they are not expanded into - # the Fields table). - # By default, workitem attributes are expanded. - # - attr :compact_workitems, true - - # - # This is the method called by the OpenWFEru engine to hand a - # workitem to this participant. - # - def consume (workitem) - - if compact_workitems - workitem.attributes["compact_workitems"] = true - end - - Workitem.from_owfe_workitem workitem - end - - # - # Called by the engine when the whole process instance (or just the - # segment of it that sports this participant) is cancelled. - # Will removed the workitem with the same fei as the cancelitem - # from the database. - # - # No expression will be raised if there is no corresponding workitem. - # - def cancel (cancelitem) - - Workitem.destroy_all([ "fei = ?", cancelitem.fei.to_s ]) - # note that delete_all was not removing workitem fields - end - - # - # When the activity/work/operation whatever is over and the flow - # should resume, this is the method to use to hand back the [modified] - # workitem to the [local] engine. - # - def reply_to_engine (workitem) - - super workitem.as_owfe_workitem - # - # replies to the workflow engine - - workitem.destroy - # - # removes the workitem from the database - end - end - - # - # An extension of ActiveParticipant. It has a 'store_name' and it - # makes sure to flag every workitem it 'consumes' with that name - # (in its 'store_name' column/field). - # - # This is the participant used mainly in 'densha' for human users. - # - class ActiveStoreParticipant < ActiveParticipant - include Enumerable - - def initialize (store_name) - - super() - @store_name = store_name - end - - # - # This is the method called by the OpenWFEru engine to hand a - # workitem to this participant. - # - def consume (workitem) - - if compact_workitems - workitem.attributes["compact_workitems"] = true - end - - Workitem.from_owfe_workitem(workitem, @store_name) - end - - # - # Iterates over the workitems currently in this store. - # - def each (&block) - - return unless block - - wis = Workitem.find_by_store_name @store_name - - wis.each do |wi| - block.call wi - end - end - end - -end -end +require File.dirname(__FILE__) + '/active_participants'