# #-- # Copyright (c) 2007, John Mettraux, 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. #++ # # $Id: definitions.rb 2725 2006-06-02 13:26:32Z jmettraux $ # # # "made in Japan" # # John Mettraux at openwfe.org # require 'rexml/document' require 'openwfe/utils' require 'openwfe/expressions/fe_raw' require 'openwfe/expressions/fe_utils' module OpenWFE # # A raw representation for a process definition, programmatic # process definitions are turned into trees of instances of this class. # class ProgExpRepresentation attr_reader \ :name, :attributes attr_accessor \ :children def initialize (name, attributes) super() @name = name @attributes = attributes @children = [] end # # Adds a child to this expression representation. # def << (child) @children << child end # # Always return the ProgRawExpression class. # def raw_expression_class return ProgRawExpression end # # Returns an XML string, containing the equivalent process definition # in the classical OpenWFE process definition language. # def to_s doc = REXML::Document.new() doc << to_xml s = "" doc.write(s, 0) return s end # # Returns this representation tree as an XML element (and its children). # def to_xml elt = REXML::Element.new(@name) #elt.attributes.update(@attributes) @attributes.each do |k, v| elt.attributes[k] = v end @children.each do |child| if child.kind_of? ProgExpRepresentation elt << child.to_xml else elt << REXML::Text.new(child.to_s) end end return elt end # # Returns a string containing the ruby code that generated this # raw representation tree. # def to_code_s (indentation = 0) s = "" tab = " " ind = tab * indentation s << ind s << OpenWFE::make_safe(@name) sa = "" @attributes.each do |k, v| sa << ", :#{OpenWFE::to_underscore(k)} => '#{v}'" end s << sa[1..-1] if sa.length > 0 if @children.length > 0 s << " do\n" @children.each do |child| if child.respond_to? :to_code_s s << child.to_code_s(indentation + 1) else s << ind s << tab s << "'#{child.to_s}'" end s << "\n" end s << ind s << "end" end return s end end # # This is the class to extend to create a programmatic process definition. # # A short example : # # class MyProcessDefinition < OpenWFE::ProcessDefinition # def make # process_definition :name => "test1", :revision => "0" do # sequence do # set :variable => "toto", :value => "nada" # print "toto:${toto}" # end # end # end # end # # li = OpenWFE::LaunchItem.new(MyProcessDefinition) # engine.launch(li) # # class ProcessDefinition def self.metaclass; class << self; self; end; end attr_reader :context def initialize () super() @context = Context.new end def method_missing (m, *args, &block) #puts "__i_method_missing >>>#{m}<<<<" ProcessDefinition.make_expression( @context, OpenWFE::to_expression_name(m), ProcessDefinition.pack_args(args), &block) end def self.method_missing (m, *args, &block) #puts "__c_method_missing >>>#{m}<<<<" @ccontext = Context.new() unless @ccontext ProcessDefinition.make_expression( @ccontext, OpenWFE::to_expression_name(m), ProcessDefinition.pack_args(args), &block) end # # This method has to be overriden in order to define # a process definition. # def make raise "make() implementation is missing, please provide one" end def self.make_expression (context, exp_name, params, &block) string_child = nil attributes = OpenWFE::SymbolHash.new #puts " ... params.class is #{params.class}" if params.kind_of? Hash params.each do |k, v| #attributes[k.to_s] = v.to_s attributes[OpenWFE::symbol_to_name(k.to_s)] = v.to_s end elsif params string_child = params.to_s end exp = ProgExpRepresentation.new(exp_name, attributes) exp.children << string_child \ if string_child if context.parent_expression # # adding this new expression to its parent # context.parent_expression << exp else # # an orphan, a top expression # context.top_expressions << exp end return exp if not block context.push_parent_expression(exp) result = block.call exp.children << result \ if result and result.kind_of? String context.pop_parent_expression return exp end # # A class method for actually "making" the process # segment raw representation # def ProcessDefinition.do_make () context = nil if @ccontext context = @ccontext else pdef = self.new pdef.make context = pdef.context end exp = context.top_expression return exp if exp name, revision = extract_name_and_revision(self.metaclass.to_s[8..-2]) attributes = {} attributes["name"] = name attributes["revision"] = revision top_expression = ProgExpRepresentation.new( "process-definition", attributes) top_expression.children = context.top_expressions return top_expression end protected def ProcessDefinition.pack_args (args) return args[0] if args.length == 1 a = {} args.each_with_index do |arg, index| if arg.is_a? Hash a = a.merge(arg) break end a[index.to_s] = arg end a end def ProcessDefinition.extract_name_and_revision (s) #puts "s is >#{s}<" m = Regexp.compile(".*::([^0-9_]*)_*([0-9][0-9_]*)$").match(s) return [ as_name(m[1]), as_revision(m[2]) ] if m m = Regexp.compile(".*::(.*$)").match(s) return [ as_name(m[1]), '0' ] if m return [ as_name(s), '0' ] end def ProcessDefinition.as_name (s) return s[0..-11] if s.match(".*Definition$") return s end def ProcessDefinition.as_revision (s) s.gsub("_", ".") end class Context attr_accessor :parent_expression attr_reader :top_expressions, :previous_parent_expressions def initialize @parent_expression = nil @top_expressions = [] @previous_parent_expressions = [] end # # puts the current parent expression on top of the 'previous # parent expressions' stack, the current parent expression # is replaced with the supplied parent expression. # def push_parent_expression (exp) @previous_parent_expressions.push(@parent_expression) \ if @parent_expression @parent_expression = exp end # # Replaces the current parent expression with the one found # on the top of the previous parent expression stack (pop). # def pop_parent_expression @parent_expression = @previous_parent_expressions.pop end # # This method returns the top expression among the # top expressions... # def top_expression return nil if @top_expressions.size > 1 exp = @top_expressions[0] return exp if exp.name == "process-definition" return nil end end end # # The actual 'programmatic' raw expression. # Its raw_representation being an instance of ProgExpRepresentation. # class ProgRawExpression < RawExpression attr_accessor \ :raw_representation def initialize \ (fei, parent_id, env_id, application_context, raw_representation) super( fei, parent_id, env_id, application_context, raw_representation) end protected def extract_attributes () raw_representation.attributes end def extract_descriptions () result = [] raw_representation.children.each do |child| next unless child.is_a?(ProgExpRepresentation) next if child.name.intern != :description lang = child.attributes[:language] lang = child.attributes[:lang] unless lang lang = "default" unless lang result << [ lang, child.children[0] ] end result end def extract_children () i = 0 result = [] raw_representation.children.each do |child| if child.kind_of? ProgExpRepresentation cname = child.name.intern next if cname == :param next if cname == :parameter next if cname == :description cfei = @fei.dup cfei.expression_name = child.name cfei.expression_id = "#{cfei.expression_id}.#{i}" efei = @environment_id rawexp = ProgRawExpression\ .new(cfei, @fei, efei, @application_context, child) get_expression_pool.update(rawexp) i = i + 1 result << rawexp.fei else result << child end end result end def extract_text_children () raw_representation.children.collect do |child| next if child.is_a? ProgExpRepresentation child.to_s end end def extract_parameters () r = [] raw_representation.children.each do |child| next unless child.is_a? ProgExpRepresentation name = child.name.intern next unless (name == :parameter or name == :param) r << Parameter.new( child.attributes[:field], child.attributes[:match], child.attributes[:default], child.attributes[:type]) end r end end private # # OpenWFE process definitions do use some # Ruby keywords... The workaround is to put an underscore # just before the name to 'escape' it. # # 'undo' isn't reserved by Ruby, but lets keep it in line # with 'do' and 'redo' that are. # KEYWORDS = [ :if, :do, :redo, :undo, :print, :sleep, :loop, :break, :when ] # # Ensures the method name is not conflicting with Ruby keywords # and turn dashes to underscores. # def OpenWFE.make_safe (method_name) method_name = OpenWFE::to_underscore(method_name) return "_" + method_name \ if KEYWORDS.include? eval(":"+method_name) return method_name end def OpenWFE.to_expression_name (method_name) method_name = method_name.to_s method_name = method_name[1..-1] if method_name[0, 1] == "_" method_name = OpenWFE::to_dash(method_name) return method_name end end