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'