#-- # Copyright (c) 2008-2009, Torsten Schönebaum, 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 Europe and Japan. #++ require 'activeresource' # gem activeresource require 'ruote/engine/context' require 'ruote/part/local_participant' module Ruote module ActiveResource # # Ruote participant which does a REST call. It's using ActiveResource to do # the actual magic. You can call every class and instance method # ActiveResource::Base and its mixins provide. You can also process the # response of the REST request and save some of its data into the workitem # for example. # # Before using this participant you *really* should make yourself familiar # with ActiveResource and its usage. # # ==Using it # # The participant should be registered in the engine in a way like this: # # engine.register_participant( # 'reminder', # ActiveResourceParticipant.new( # { # :site => 'http://localhost:7000', # :resource_name => 'story', # :method => 'put', # we will use ActiveResource::CustomMethods::InstanceMethods#put # :argument => 'send_reminder', # put is happy with just one # # parameter: The action to be # # called. So we won't have to use a # # block to set the arguments of the # # ActiveResource method to call. # } # ) # ) # # Let's try a more complex example: # # engine.register_participant( # 'complex_ares', # ActiveResourceParticipant.new( # { # :site => 'http://localhost:7000', # :resource_name => 'story', # :method => 'find', # yes, even ActiveResource::Base#find method # # may be used... # :response_handling => proc do |response, workitem| # define response # # handling block # workitem.set_attribute('title', response.attributes['title']) # # The type of the response object depends on # # the method you use! # end # } # ) do |workitem| # define arguments to use with the find method # # each argument is an entry in an array the block has to return # [ # workitem.attributes['story_id'], # the first argument # {:prefix => 'foo'} # the second one is an hash # ] # end # ) # # You can now use the participant in your workflow definitions. An example # using XML: # # # # # # # # # Here, the ActiveResourceParticipant is used to send a reminder. # # *Note:* prefer configuration of the participant at registration time, # information like 'method', 'site' and 'resource_name' may clutter the # process definition. # class ActiveResourceParticipant include Ruote::EngineContext include Ruote::LocalParticipant # # == options hash # # You have to set some default options by passing an options hash to the # new method. It should contain the following keys: # # :site:: The URI (as string) of the site where your REST # interface sits. See site parameter in ActiveResource::Base. # :resource_name:: The name of the resource you like to access. # See element_name parameter in ActiveResource::Base. # :method:: The ActiveResource method to call. In most cases # should be "get", "put", "post" or "delete". # :argument:: The first parameter which should be used as # argument to the ActiveResource method which will be called. This value # is ignored if a block is given (see below) # :resource_id:: The ID of the resource on which the action # should be called on default. If negative, nil or false, the action # will be called on the complete set of resources instead of on a single # resource determined by its ID. # :response_handling:: A proc object / block which gets called # after the request is done. It gets called with to arguments: The # response of the ActiveResource method called and the workitem. # # The new method also takes a block. It may be used to set the arguments # of the ActiveResource method to call. The block must return an array of # arguments. The array will be splitted and each entry will be used as # argument to the ActiveResource method called. The block itself takes one # or two arguments: Either only the current workitem or the workitem and # expstorage[workitem.fei]. # # All parameters except response_handling and the block set default values # -- you can always override them in the process definition. Just use the # corresponding symbol name (without ":") as attribute. # def initialize (options, &block) @options = options # some defaults @options[:site] ||= 'http://127.0.0.1:3000' @options[:method] ||= :get @block = block end # # This method is called each time the participant receives a workitem. It # calls the requested ActiveResource method, calls the response handling # code if present and then immediatly replies to the engine. # def consume (workitem) # use block to determine the method's arguments if given args = if @block if @block.arity == 1 @block.call(workitem) else @block.call(workitem, expstorage[workitem.fei]) end elsif a = param(workitem, :arg) || param(workitem, :argument) Array(a) else [] # no arguments end # create new subclass of ActiveResource::Base active_resource_class = Class.new(::ActiveResource::Base) active_resource_class.site = param(workitem, :site) # set site parameter active_resource_class.element_name = param(workitem, :resource_name) # set element name # Do we work on a single or on a set of resources? If resource_id is nil # or negative, it's a set of resources. resource_id = param(workitem, :resource_id) active_resource = if (!resource_id) || (resource_id.to_i < 0) # set of resources active_resource_class else # specific resource active_resource_class.new(:id => resource_id) end response = active_resource.send(param(workitem, :method), args) # we got our response, but what to do with it? if (h = param(workitem, :response_handling)).is_a?(Proc) h.call(response, workitem) end # reply to the engine reply_to_engine(workitem) end # Does nothing. def cancel (fei, flavour) end protected def param (workitem, key) workitem.fields['params'][key.to_s] || @options[key] end end end end