Class: Arrow::Applet

Inherits:
Object show all
Defined in:
lib/arrow/applet.rb

Overview

An abstract base class for Arrow applets. Provides execution logic, argument-parsing/untainting/validation, and templating through an injected factory.

Synopsis

  require 'arrow/applet'

class MyApplet < Arrow::Applet

  applet_name "My Applet"
  applet_description 'Displays a block of whatever character is ' +
    'passed as argument'
  applet_maintainer 'Michael Granger <mgranger@rubycrafters.com>'
  applet_version '1.01'
  default_action :form

      # Define the 'display' action
      def_action :display do |txn|
          char = txn.vargs[:char] || 'x'
          char_page = self.make_character_page( char )
          templ = self.load_template( :main )
          templ.char_page = char_page

          return templ
      end
  template :main, "main.tmpl"

      # Define the 'form' action -- display a form that can be used to set
      # the character the block is composed of. Save the returned proxy so
      # the related signature values can be set.
      formaction = def_action :form do |txn|
          templ = self.load_template( :form )
          templ.txn = txn
          return templ
      end
      formaction.template = "form.tmpl"

      # Make a page full of character +char+.
      def make_character_page( char )
          page = ''
          40.times do
              page << (char * 80) << "\n"
          end
      end

end

Subversion Id

 $Id$

Authors

  • Michael Granger

:include: LICENSE

Please see the file LICENSE in the BASE directory for licensing details.

Direct Known Subclasses

Service

Defined Under Namespace

Classes: SigProxy, SignatureStruct

Constant Summary

SignatureStructDefaults =

Default-generators for Signatures which are missing one or more of the optional pairs.

{
  :name       => proc {|rawsig, klass| klass.name},
  :description    => "(none)",
  :maintainer     => "", # Workaround for RDoc
  :version      => nil, # Workaround for RDoc
  :default_action   => '_default',
  :config       => {},
  :templates      => {},
  :validator_profiles => {
    :__default__   => {
      :optional     => [:action],
      :constraints    => {
        :action => /^\w+$/,
      },
    },
  }
}

Class Attribute Summary

Instance Attribute Summary

Class Method Summary

Instance Method Summary

Methods inherited from Object

deprecate_class_method, deprecate_method

Methods included from Loggable

#log

Constructor Details

- (Applet) initialize(config, template_factory, uri)

Create a new Arrow::Applet object with the specified config (an Arrow::Config object), template_factory (an Arrow::TemplateFactory object), and the uri the applet will live under in the appserver (a String).



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/arrow/applet.rb', line 428

def initialize( config, template_factory, uri )
  @config       = config
  @template_factory = template_factory
  @uri        = uri

  @signature      = self.class.signature.dup
  @run_count      = 0
  @total_utime    = 0
  @total_stime    = 0

  # Make a regexp out of all public <something>_action methods
  @actions = self.public_methods( true ).
    select {|meth| /^(\w+)_action$/ =~ meth }.
    collect {|meth| meth.gsub(/_action/, '') }
  @actions_regexp = Regexp.new( "^(" + actions.join( '|' ) + ")$" )
end

Class Attribute Details

+ (Object) derivatives (readonly)

The Array of loaded applet classes (derivatives)



193
194
195
# File 'lib/arrow/applet.rb', line 193

def derivatives
  @derivatives
end

+ (Object) filename

The file containing the applet’s class definition



200
201
202
# File 'lib/arrow/applet.rb', line 200

def filename
  @filename
end

+ (Object) newly_loaded (readonly)

The Array of applet classes that were loaded by the most recent call to .load.



197
198
199
# File 'lib/arrow/applet.rb', line 197

def newly_loaded
  @newly_loaded
end

Instance Attribute Details

- (Object) actions (readonly)

The list of all valid actions on the applet



472
473
474
# File 'lib/arrow/applet.rb', line 472

def actions
  @actions
end

- (Object) config

The Arrow::Config object which contains the system’s configuration.



451
452
453
# File 'lib/arrow/applet.rb', line 451

def config
  @config
end

- (Object) run_count (readonly)

The number of times this particular applet object has been run



460
461
462
# File 'lib/arrow/applet.rb', line 460

def run_count
  @run_count
end

- (Object) signature (readonly)

The Struct that contains the configuration values for this applet



457
458
459
# File 'lib/arrow/applet.rb', line 457

def signature
  @signature
end

- (Object) template_factory (readonly)

The Arrow::TemplateFactory object used to load templates for the applet.



469
470
471
# File 'lib/arrow/applet.rb', line 469

def template_factory
  @template_factory
end

- (Object) total_stime (readonly)

The number of system seconds spent in this applet’s #run method.



466
467
468
# File 'lib/arrow/applet.rb', line 466

def total_stime
  @total_stime
end

- (Object) total_utime (readonly)

The number of user seconds spent in this applet’s #run method.



463
464
465
# File 'lib/arrow/applet.rb', line 463

def total_utime
  @total_utime
end

- (Object) uri (readonly)

The URI the applet answers to



454
455
456
# File 'lib/arrow/applet.rb', line 454

def uri
  @uri
end

Class Method Details

+ (Object) applet_description(desc)

Set the description of the applet to desc.



230
231
232
# File 'lib/arrow/applet.rb', line 230

def self::applet_description( desc )
  self.signature.description = desc
end

+ (Object) applet_maintainer(info)

Set the contact information for the maintainer of the applet to info.



236
237
238
# File 'lib/arrow/applet.rb', line 236

def self::applet_maintainer( info )
  self.signature.maintainer = info
end

+ (Object) applet_name(name)

Set the name of the applet to name.



224
225
226
# File 'lib/arrow/applet.rb', line 224

def self::applet_name( name )
  self.signature.name = name
end

+ (Object) applet_version(ver)

Set the contact information for the maintainer of the applet to info.



242
243
244
# File 'lib/arrow/applet.rb', line 242

def self::applet_version( ver )
  self.signature.version = ver
end

+ (Object) def_action(name, &block)

Define an action for the applet. Transactions which include the specified name as the first directory of the uri after the one the applet is assigned to will be passed to the given block. The return value from this method is an Arrow::Applet::SigProxy which can be used to set associated values in the applet’s Signature; see the Synopsis in lib/arrow/applet.rb for examples of how to use this.



401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/arrow/applet.rb', line 401

def self::def_action( name, &block )
  name = '_default' if name.to_s.empty?

  # Action must accept at least a transaction argument
  unless block.arity.nonzero?
    raise ScriptError,
      "Malformed action #{name}: must accept at least one argument"
  end

  methodName = "#{name}_action"
  define_method( methodName, &block )
  SigProxy.new( name, self )
end

+ (Object) default_action(action)

Set the default action for the applet to action.



248
249
250
# File 'lib/arrow/applet.rb', line 248

def self::default_action( action )
  self.signature.default_action = action.to_s
end

+ (Object) inherited(klass)

Inheritance callback: register any derivative classes so they can be looked up later.



267
268
269
270
271
272
273
274
275
# File 'lib/arrow/applet.rb', line 267

def self::inherited( klass )
  @inherited_from = true
  if defined?( @newly_loaded )
    @newly_loaded.push( klass )
    super
  else
    Arrow::Applet.inherited( klass )
  end
end

+ (Boolean) inherited_from?

Have any subclasses of this class been created?

Returns:

  • (Boolean)


279
280
281
# File 'lib/arrow/applet.rb', line 279

def self::inherited_from?
  @inherited_from
end

+ (Object) load(filename, include_base_classes = false)

Load any applet classes in the given file and return them. Ignores any class which has a subclass in the file unless include_base_classes is set false



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/arrow/applet.rb', line 297

def self::load( filename, include_base_classes=false )
  self.newly_loaded.clear

  # Load the applet file in an anonymous module. Any applet classes get
  # collected via the ::inherited hook into @newly_loaded
  Kernel.load( filename, true )

  newderivatives = @newly_loaded.dup
  @derivatives -= @newly_loaded
  @derivatives.push( *@newly_loaded )

  newderivatives.each do |applet|
    applet.filename = filename
  end

  unless include_base_classes
    newderivatives.delete_if do |applet|
      applet.inherited_from?
    end
  end

  return newderivatives
end

+ (Object) make_signature

Signature lookup: look for either a constant or an instance variable of the class that contains the raw signature hash, and convert it to an Arrow::Applet::SignatureStruct object.



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/arrow/applet.rb', line 345

def self::make_signature
  rawsig = nil
  if self.instance_variables.include?( "@signature" )
    rawsig = self.instance_variable_get( :@signature )
  elsif self.constants.include?( "Signature" )
    rawsig = self.const_get( :Signature )
  elsif self.constants.include?( "SIGNATURE" )
    rawsig = self.const_get( :SIGNATURE )
  else
    rawsig = {}
  end

  # Backward-compatibility: Rewrite the 'vargs' member as
  # 'validator_profiles' if 'vargs' exists and 'validator_profiles'
  # doesn't. 'vargs' member will be deleted regardless.
  rawsig[ :validator_profiles ] ||= rawsig.delete( :vargs ) if
    rawsig.key?( :vargs )

  # If the superclass has a signature, inherit values from it for
  # pairs that are missing.
  if self.superclass < Arrow::Applet && self.superclass.signature?
    self.superclass.signature.each_pair do |member,value|
      next if [:name, :description, :version].include?( member )
      if rawsig[member].nil?
        rawsig[ member ] = value.dup rescue value
      end
    end
  end

  # Apply sensible defaults for members that aren't defined
  SignatureStructDefaults.each do |key,val|
    next if rawsig[ key ]
    case val
    when Proc, Method
      rawsig[ key ] = val.call( rawsig, self )
    when Numeric, NilClass, FalseClass, TrueClass
      rawsig[ key ] = val
    else
      rawsig[ key ] = val.dup
    end
  end

  # Signature = Struct.new( :name, :description, :maintainer,
  #   :version, :config, :default_action, :templates, :validatorArgs,
  #   :monitors )
  members = SignatureStruct.members.collect {|m| m.to_sym}
  return SignatureStruct.new( *rawsig.values_at(*members) )
end

+ (Object) method_added(sym)

Method definition callback: Check newly-defined action methods for appropriate arity.



286
287
288
289
290
291
# File 'lib/arrow/applet.rb', line 286

def self::method_added( sym )
  if /^(\w+)_action$/.match( sym.to_s ) &&
      self.instance_method( sym ).arity.zero?
    raise ScriptError, "Inappropriate arity for #{sym}", caller(1)
  end
end

+ (Object) normalized_name

Return the name of the applet class after stripping off any namespace-safe prefixes.



324
325
326
# File 'lib/arrow/applet.rb', line 324

def self::normalized_name
    self.name.sub( /#<Module:0x\w+>::/, '' )
end

+ (Object) signature

Get the applet’s signature (an Arrow::Applet::SignatureStruct object).



331
332
333
# File 'lib/arrow/applet.rb', line 331

def self::signature
  @signature ||= make_signature()
end

+ (Boolean) signature?

Returns true if the applet class has a signature.

Returns:

  • (Boolean)


337
338
339
# File 'lib/arrow/applet.rb', line 337

def self::signature?
  !self.signature.nil?
end

+ (Object) template(sym, path = nil) Also known as: templates

Set the path for the template specified by sym to path.



205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/arrow/applet.rb', line 205

def self::template( sym, path=nil )
  case sym
  when Symbol, String
    self.signature.templates[ sym ] = path

  when Hash
    self.signature.templates.merge!( sym )

  else
    raise ArgumentError, "cannot convert %s to Symbol" % [ sym ]
  end
end

+ (Object) validator(action, rules = {})

Set the validator rules for the specified action.



255
256
257
258
259
260
261
262
# File 'lib/arrow/applet.rb', line 255

def self::validator( action, rules={} )
  if action.is_a?( Hash ) && rules.empty?
    Arrow::Logger[ self ].debug "Assuming hash syntax for validation definition: %p" % [ action ]
    action, rules = *action.to_a.first
  end
  Arrow::Logger[ self ].debug "Defining validator for action %p with rules %p" % [ action, rules ]
  self.signature.validator_profiles[ action ] = rules
end

Instance Method Details

- (Object) action_missing_action(txn, raction, *args)

The action invoked if the specified action is not explicitly defined. The default implementation will look for a template with the same key as the action, and if found, will load that and return it.

Raises:

  • (Arrow::AppletError)


515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/arrow/applet.rb', line 515

def action_missing_action( txn, raction, *args )
  self.log.debug "In action_missing_action with: raction = %p, args = %p" %
    [ raction, args ]

  if raction && raction.to_s =~ /^([a-z]\w+)$/
    tmplkey = $1.untaint
    self.log.debug "tmpl is: %p (%stainted)" %
      [ tmplkey, tmplkey.tainted? ? "" : "not " ]

    if @signature.templates.key?( tmplkey.to_sym )
      self.log.debug "Using template sender default action for %s" % raction
      txn.vargs = self.make_validator( tmplkey, txn )

      tmpl = self.load_template( raction.to_sym )
      tmpl.txn = txn
      tmpl.applet = self

      return tmpl
    end
  end

  raise Arrow::AppletError, "No such action '%s' in %s" %
    [ raction, self.signature.name ]
end

- (Object) average_usage

Returns the average number of seconds (user + system) per run.



554
555
556
557
# File 'lib/arrow/applet.rb', line 554

def average_usage
  return 0.0 if @run_count.zero?
  (@total_utime + @total_stime) / @run_count.to_f
end

- (Object) call_action_method(txn, action, *args) (protected)

Invoke the specified action (an object that responds to #arity and #call) with the given txn and the args which it can accept based on its arity.



627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/arrow/applet.rb', line 627

def call_action_method( txn, action, *args )
    self.log.debug "Applet action arity: %d; args = %p" %
      [ action.arity, args ]

    # Invoke the action with the right number of arguments.
    if action.arity < 0
      return action.call( txn, *args )
    elsif action.arity >= 1
      args.unshift( txn )
      until args.length >= action.arity do args << nil end
      return action.call( *(args[0, action.arity]) )
    else
      raise Arrow::AppletError,
        "Malformed action: Must accept at least a transaction argument"
    end
  end


  ### Run an action with a duped transaction (e.g., from another action)
  def subrun( action, txn, *args )
    action, txn = txn, action if action.is_a?( Arrow::Transaction )

    txn.vargs ||= self.make_validator( action, txn )
    action = self.method( "#{action}_action" ) unless action.respond_to?( :arity )
    self.log.debug "Running subordinate action '%s' from '%s'" %
      [ action, caller[0] ]

    return self.call_action_method( txn, action, *args )
  end


  ### Load and return the template associated with the given +key+ according
  ### to the applet's signature. Returns +nil+ if no such template exists.
  def load_template( key )
    tname = @signature.templates[key] or
      raise Arrow::AppletError, 
        "No such template %p defined in the signature for %s (%s)" %
        [ key, self.signature.name, self.class.filename ]

    tname.untaint

    return @template_factory.get_template( tname )
  end
  alias_method :template, :load_template


  ### Create a FormValidator object for the specified +action+ which has
  ### been given the arguments from the given +txn+.
  def make_validator( action, txn )
    profile = self.get_validator_profile_for_action( action, txn ) or
      return nil

    # Create a new validator object, map the request args into a regular
    # hash, and then send them to the validaator with the applicable profile
    self.log.debug "Creating form validator for profile: %p" % [ profile ]

    params = {}

    # Only try to parse form parameters if there's a form
    if txn.form_request?
      txn.request.paramtable.each do |key,val|
        # Multi-valued vs. single params
        params[key] = val.to_a.length > 1 ? val.to_a : val.to_s
      end
    end
    validator = Arrow::FormValidator.new( profile, params )

    self.log.debug "Validator: %p" % validator
    return validator
  end


  ### Return the validator profile that corresponds to the +action+ which
  ### will be executed by the specified +txn+. Returns the __default__
  ### profile if no more-specific one is available.
  def get_validator_profile_for_action( action, txn )
    if action.to_s =~ /^(\w+)$/
      action = $1
      action.untaint
    else
      self.log.warning "Invalid action '#{action.inspect}'"
      action = :__default__
    end

    # Look up the profile for the applet or the default one
    profile = @signature.validator_profiles[ action.to_sym ] ||
      @signature.validator_profiles[ :__default__ ]

    if profile.nil?
      self.log.warning "No validator for #{action}, and no __default__. "\
        "Returning nil validator."
      return nil
    end

    return profile
  end

end

- (Boolean) delegable? Also known as: chainable?

Returns true if the receiver has a #delegate method that is inherited from somewhere other than the base Arrow::Applet class.

Returns:

  • (Boolean)


506
507
508
# File 'lib/arrow/applet.rb', line 506

def delegable?
  return self.method(:delegate).to_s !~ /\(Arrow::Applet\)/
end

- (Object) delegate(txn, chain, *args) {|chain| ... }

Wrapper method for a delegation (chained) request.

Yields:

  • (chain)


499
500
501
# File 'lib/arrow/applet.rb', line 499

def delegate( txn, chain, *args )
  yield( chain )
end

- (Object) find_action_method(txn, action, *args) (protected)

Given an action name and any other URI path args from the request, return a Method object that will handle the request, and the remaining arguments as a splatted Array.



619
620
621
622
# File 'lib/arrow/applet.rb', line 619

def find_action_method( txn, action, *args )
  self.log.debug "Mapping %s( %p ) to an action" % [ action, args ]
  return self.method( "#{action}_action" )
end

- (Object) get_action_name(txn, *args) (protected)

Get the expected action name from the specified txn and the args extracted from the URI path; return the action as a Symbol and the remaining arguments as a splatted Array.



581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
# File 'lib/arrow/applet.rb', line 581

def get_action_name( txn, *args )
  self.log.debug "Fetching the intended action name from txn = %p, args = %p" % [ txn, args ]

  name = args.shift
  name = nil if name.to_s.empty?
  name ||= @signature.default_action or
    raise Arrow::AppletError, "Missing default handler"

  if (( action = self.map_to_valid_action(name) ))
    self.log.debug "  found what looks like a valid action (%p)" % [ action ]
    return action.to_sym, *args
  else
    self.log.debug "  didn't find a valid action; returning :action_missing"
    return :action_missing, name, *args
  end
end

- (Object) get_validator_profile_for_action(action, txn) (protected)

Return the validator profile that corresponds to the action which will be executed by the specified txn. Returns the default profile if no more-specific one is available.



702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'lib/arrow/applet.rb', line 702

def get_validator_profile_for_action( action, txn )
  if action.to_s =~ /^(\w+)$/
    action = $1
    action.untaint
  else
    self.log.warning "Invalid action '#{action.inspect}'"
    action = :__default__
  end

  # Look up the profile for the applet or the default one
  profile = @signature.validator_profiles[ action.to_sym ] ||
    @signature.validator_profiles[ :__default__ ]

  if profile.nil?
    self.log.warning "No validator for #{action}, and no __default__. "\
      "Returning nil validator."
    return nil
  end

  return profile
end

- (Object) inspect

Return a human-readable String representing the applet.



542
543
544
545
546
547
548
549
550
# File 'lib/arrow/applet.rb', line 542

def inspect
  "<%s:0x%08x: %s [%s/%s]>" % [
    self.class.name,
    self.object_id * 2,
    @signature.name,
    @signature.version,
    @signature.maintainer
  ]
end

- (Object) load_template(key) (protected) Also known as: template

Load and return the template associated with the given key according to the applet’s signature. Returns nil if no such template exists.



660
661
662
663
664
665
666
667
668
669
# File 'lib/arrow/applet.rb', line 660

def load_template( key )
  tname = @signature.templates[key] or
    raise Arrow::AppletError, 
      "No such template %p defined in the signature for %s (%s)" %
      [ key, self.signature.name, self.class.filename ]

  tname.untaint

  return @template_factory.get_template( tname )
end

- (Object) make_validator(action, txn) (protected)

Create a FormValidator object for the specified action which has been given the arguments from the given txn.



675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
# File 'lib/arrow/applet.rb', line 675

def make_validator( action, txn )
  profile = self.get_validator_profile_for_action( action, txn ) or
    return nil

  # Create a new validator object, map the request args into a regular
  # hash, and then send them to the validaator with the applicable profile
  self.log.debug "Creating form validator for profile: %p" % [ profile ]

  params = {}

  # Only try to parse form parameters if there's a form
  if txn.form_request?
    txn.request.paramtable.each do |key,val|
      # Multi-valued vs. single params
      params[key] = val.to_a.length > 1 ? val.to_a : val.to_s
    end
  end
  validator = Arrow::FormValidator.new( profile, params )

  self.log.debug "Validator: %p" % validator
  return validator
end

- (Object) map_to_valid_action(action) (protected)

Map the given action name to an action that’s been declared, or else map it to a fallback action (‘action_missing’ by default).



601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/arrow/applet.rb', line 601

def map_to_valid_action( action )
  self.log.debug "trying to map %p to a valid action method" % [ action ]

  if (( match = @actions_regexp.match(action.to_s) ))
    action = match.captures[0]
    action.untaint
    self.log.debug "Matched action = #{action}"
    return action
  else
    self.log.debug "  no matching action method in %p" % [ @actions_regexp ]
    return nil
  end
end

- (Object) run(txn, *args)

Run the specified action for the given txn and the specified args.



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# File 'lib/arrow/applet.rb', line 477

def run( txn, *args )
  self.log.debug "Running %s" % [ self.signature.name ]

  return self.time_request do
    name, *newargs = self.get_action_name( txn, *args )
    txn.vargs = self.make_validator( name, txn )
    action = self.find_action_method( txn, name, *newargs )

    # Decline the request if the action isn't a callable object
    unless action.respond_to?( :arity )
      self.log.info "action method (%p) doesn't do #arity, returning it as-is." %
        [ action ]
      return action
    end

    self.log.debug "calling action method %p with args (%p)" % [ action, newargs ]
    self.call_action_method( txn, action, *newargs )
  end
end

- (Object) subrun(action, txn, *args) (protected)

Run an action with a duped transaction (e.g., from another action)



646
647
648
649
650
651
652
653
654
655
# File 'lib/arrow/applet.rb', line 646

def subrun( action, txn, *args )
  action, txn = txn, action if action.is_a?( Arrow::Transaction )

  txn.vargs ||= self.make_validator( action, txn )
  action = self.method( "#{action}_action" ) unless action.respond_to?( :arity )
  self.log.debug "Running subordinate action '%s' from '%s'" %
    [ action, caller[0] ]

  return self.call_action_method( txn, action, *args )
end

- (Object) time_request (protected)

Time the block, logging the result



565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/arrow/applet.rb', line 565

def time_request
  starttimes = Process.times
  yield
ensure
  runtimes = Process.times
  @run_count += 1
  @total_utime += utime = (runtimes.utime - starttimes.utime)
  @total_stime += stime = (runtimes.stime - starttimes.stime)
  self.log.info \
    "[PID %d] Runcount: %d, User: %0.2f/%0.2f, System: %0.2f/%0.2f" %
    [ Process.pid, @run_count, utime, @total_utime, stime, @total_stime ]
end