eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# AAFID::ControllerEntity
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, Feb 25, 1998.
#
# $Id: ControllerEntity.pm,v 1.28 1999/09/03 17:08:53 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#

package AAFID::ControllerEntity;

# The following keeps up with the RCS version number. The self-assignment
# keeps the -w switch from complaining (because $VERSION may not be used
# in this file)
$VERSION = do { my @r = (q$Revision: 1.28 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; $VERSION = $VERSION;

%PARAMETERS = (
	       Description	=> "Base ControllerEntity class",
	      );

use vars qw(
	    @ISA
	    $VERSION
	    %PARAMETERS
	   );
use strict;
use Carp;
use Cwd;
use File::Basename;
use File::Path;
use AAFID::Config;
use AAFID::Common;
use AAFID::Entity;
use AAFID::Log;
use AAFID::Comm;
use AAFID::Message;
use Comm::Reactor;
use Data::Dumper;

@ISA=qw(AAFID::Entity
	AAFID::Log);

=pod

This is the base class for entities that can control other entities. By
"control" we mean:

=over 4

=item 1 

The ability to start/stop them.

=item 2

The ability to communicate with them as their "up" entity, send commands
to them, and receive their output.

=back 4

The two obvious examples of this type of entity are transceivers and
monitors.

A B<ControllerEntity> has all the functionality of a normal B<Entity>, but
with some added things to allow for the operations described above.

=head1 Initialization

=cut

  BEGIN {
  }

sub _init {
  my $self=checkref(shift);
  # Initialize the cache of loaded classes to empty.
  $self->setParameter(LoadedClasses => {});
  # Call user initialization.
  $self->Init;
  # Create a temporary directory for code we receive from above.
  # TODO: Make this configurable and secure.
  my $tmpdir=$self->getParameter("TmpCodeBaseDir") || 
             $self->getParameter("TmpDir");
  my $tmpdirname=
    $self->getParameter("TmpCodeDirName") || "ControllerEntityCode";
  if (!$tmpdir) {
    $tmpdir=$ENV{TMPDIR} || 
            $ENV{TEMPDIR} || 
	    $ENV{TMP} || 
	    $ENV{TEMP} ||
	    "/tmp";
  }
  $tmpdir .= "/$tmpdirname.$$";
  $self->setParameter(CodeCacheDir => $tmpdir);
  if (-e $tmpdir) {
    if (! -d $tmpdir || ! -w $tmpdir) {
      $self->Log("errors", "Cannot use directory $tmpdir, dying.\n");
      exit(1);
    }
    else {
      $self->Log("debug", "Using directory $tmpdir for temporary code storage.\n");
    }
  }
  else {
    mkpath($tmpdir, 0, oct("700"));
    if (!(-d $tmpdir && -w $tmpdir)) {
      $self->Log("errors", "Could not create $tmpdir, dying.\n");
      exit(1);
    }
    $self->Log("debug", "Created directory $tmpdir for temporary code storage.\n");
  }
  unshift @INC, $tmpdir;
  return $self;
}

=head1 Starting other entities

The main additional functionality of a controller entity with respect
to a regular entity is the ability to start other entities and interact
with them.

The C<invoke> method provides this functionality. Given the description
of where to find an entity, it starts it as a separate process (and
possibly in a different machine), sets up the communication handles,
updates the appropriate parameters, and sets it to run.

C<invoke> receives as an argument a string containing the location of
the entity to start. If it can successfully start the entity, it
returns the current entity and updates its C<OpenHandles> and
C<OpenHandleIDs> parameters, otherwise it returns C<undef>. If the 
entity cannot be found but a request for it is sent up, C<invoke>
returns -1.

Starting the entity involves sending it a CONNECT PARENT message, with
the parent information, so that the entity can update its tables.

The argument can have any of the following forms:

=over 4

=item "classname"

Looks for a class with the given name in the current search path (see
the description of the appropriate parameters below). A full class
qualification (with '::') can be requested. The class must be a
subclass of C<AAFID::Entity>. If found, a new instance of the entity
is created, its ID recorded, and the C<run> method of the new entity
is invoked in a new local process.

In this case the command used to load the class is C<use classname>.

=item "/absolute/path/to/classname"

Same as above, but loads the class from the specified absolute
pathname.

The command used to load the class is

  require '/absolute/path/to/classname'; import classname;

=back 4

=cut

sub invoke {
  my $self=checkref(shift);
  my $host_regex='[\w-]+(?:\.[\w-]+)*';
  my $filename_regex='[\w.-]+';
  my $classpath_regex="$filename_regex(?:::$filename_regex)*";
  my $path_regex="/$filename_regex(?:/$filename_regex)*/";
  my $result;

  $_=shift;
  my $cmds=shift;

  # Check what kind of locator we are given.
CHECK_LOCATOR: {
    m/^($classpath_regex)$/ && do {
	$self->Log("processes", "Got a request to load local package $1\n");
	$result=$self->_invoke($1, $cmds);
	last CHECK_LOCATOR;
    };
    m/^($path_regex)($filename_regex)$/ && do {
	$self->Log("processes", "Got a request to load local file $1$2\n");
	$result=$self->_invoke($1, $2, $cmds);
	last CHECK_LOCATOR;
    };
  DEFAULT:
    $self->Log("errors", "Bad Entity locator in request: $_\n");
    return undef;
  }
  return $result;
}

sub _needtoload {
  my $self=checkref(shift);
  my $classname=shift or return 0;
  my $trclassname=$classname;
  $trclassname =~ s!::!/!g;
  # Check the loaded classes cache to see if the requested class has
  # already been loaded. Check both in our own cache and in Perl's %INC.
  return !(defined($self->getParameter('LoadedClasses')->{$classname}) ||
	   defined($INC{"$trclassname"}) ||
	   defined($INC{"${trclassname}.pm"}) || 
	   defined($INC{"${trclassname}.pl"}));
}

sub _putincache {
  my $self=checkref(shift);
  my $classname=shift or return undef;
  # Put the class in the cache by creating an element hashed by it.
  $self->getParameter('LoadedClasses')->{$classname}=1;
  return $self;
}

sub _invoke {
  my $self=checkref(shift);
  my $what_to_load;
  my $loadresult;
  my $classname;

  # Find what we have to load
  if ($#_==1) { # Just one argument, plain class name.
    ($classname = shift) =~ s/\.pm$//;
    $what_to_load=$classname;
  }
  else { # First parameter dirname, second filename
    my $dirname=shift;
    my $filename=shift;
    ($classname=$filename) =~ s/\.pm//;
    ($what_to_load=$dirname.$filename) =~ s/\.pm$//;
  }
  my $cmds=shift;

  # Try to load the class.
  $loadresult=$self->loadmodule($what_to_load);
  # If the class could not be found (and thus a request for it was
  # sent up), mark it as pending.
  if ($loadresult==-1) {
    if (!$self->getParameter('_PendingRuns')) {
      $self->setParameter(_PendingRuns => {});
    }
    $self->Log("debug", "Setting $what_to_load as a pending run.\n");
    $self->getParameter('_PendingRuns', {})->{$what_to_load}=1;
    $self->getParameter('_PendingCmds', [])->{$what_to_load}=$cmds;
    return -1;
  }
  # An error occurred.
  elsif (!defined($loadresult)) {
    $self->Log("errors", "Error loading $what_to_load: $@\n");
    return undef;
  }
  # Load successful.
  else {
    # We were successful, go ahead.
  }

  # If we get here, the load was successful.
  # Create the object and run it.
  if (!$self->_instantiate_and_run($classname, $cmds)) {
    $self->Log("errors",
	       "Error creating instance of $classname: $@\n");
    return undef;
  }
  return $self;
}

=pod

The C<_instantiate_and_run> subroutine assumes that the classname whose name
it is given as parameter is already loaded, and simply creates an instance and
runs it. It must be a subclass of C<AAFID::Entity> (the subroutine checks
for it).

=cut

sub _instantiate_and_run {
  my $self=checkref(shift);
  my $classname=shift;
  my $cmds=shift;
  if (!$classname) {
    $self->Log("errors", "Null classname given to _instantiate_and_run.\n");
    $@="Null classname given to _instantiate_and_run.";
    return undef;
  }
  # Now we got the class loaded, and the package name in $classname.
  # Check if it is a valid entity.
  unless ($classname->isa("AAFID::Entity")) {
    $self->Log("errors", 
	       "$classname is not a derivative of AAFID::Entity\n");
    $@="$classname is not a derivative of AAFID::Entity\n";
    return undef;
  }
  # Now, the magic moment. Create a new instance of the class we
  # just loaded.
  my $newentity=$classname->new;
  if (!$newentity) {
    $@="Error creating new entity of type $classname: $@";
    return undef;
  }

  # See if the new entity needs any filters, and set them up.
  if ($self->setupFilters($newentity, $cmds)) {
    return $self->_fork_and_run($newentity, $classname, $cmds);
  }
  return $self;
}

sub _fork_and_run {
  my $self=checkref(shift);
  my $newentity=shift;
  my $classname=shift;
  my $cmds=shift;

  # And now the even more magic moment. The steps are:
  # 1. Create two pipes (one in each direction).
  # 2. Fork a new process.
  # 3. Both the parent and the child keep one reading end and one
  #    writing end.
  # 4. The parent stores its reading end in its OpenHandles parameter,
  #    and the writing end in its OpenHandleIDs parameter, associated
  #    with the new entity's ID.
  # 4b. The parent destroys its copy of the new entity, because it
  #    will not be used there.
  # 5. The parent sends the child a CONNECT PARENT message.
  # 6. The child associates its reading and writing ends with STDIN
  #    and STDOUT, respectively.
  # 7. The child calls the run() method of the new entity.
  pipe RDRPAR, WRTCHILD 
    or do { $self->Log("errors", "Error creating pipe: $!\n");
	    $@="Error creating pipe: $!";
	    return undef;
	  };
  pipe RDRCHILD, WRTPAR 
    or do { $self->Log("errors", "Error creating pipe: $!\n");
	    $@="Error creating pipe: $!";
	    return undef;
	  };
  my $pid;
  if (defined($pid=fork)) {
    if ($pid) {
      # In the parent...
      # Close the child's ends of the pipes.
      close(WRTCHILD) 
	or do { $self->Log("errors", "Error closing in parent: $!\n");
		$@="Error closing in parent: $!";
		return undef;
	      };
      close(RDRCHILD) 
	or do { $self->Log("errors", "Error closing in parent: $!\n");
		$@="Error closing in parent: $!";
		return undef;
	      };
      # Construct IO::File objects around our ends of the pipe.
      my $rdr=IO::File->new_from_fd(\*RDRPAR, "r");
      my $wrt=IO::File->new_from_fd(\*WRTPAR, "w");
      if (!$rdr || !$wrt) {
	$self->Log("errors", "Error creating IO::File objects: $!");
	return undef;
      }
      # Make the writing end non-buffered.
      $wrt->autoflush(1);
      # Add the reading end to our OpenHandles parameter.
      my $openhandles=$self->getParameter('OpenHandles');
      if (!$openhandles) {
	croak "Panic: I don't have my OpenHandles parameter!";
      }
      $openhandles->add($rdr);
      # Add an event handler for the reading end.
      $self->Log("debug", "Adding callback for handle $rdr (reading end of parent process)\n");
      Comm::Reactor::add_handle($rdr,
	       sub {
		 my ($fh, $msg)=@_;
		 $self->processInput($fh, AAFID::Comm::nextMsg($fh, $msg));
	       });
      # Create a record in our OpenHandleIDs parameter for the
      # new entity, including its writing handle, its description
      # and class name.
      my $openhandleIDs=$self->getParameter('OpenHandleIDs');
      if (!$openhandleIDs) {
	croak "Panic: I don't have my OpenHandleIDs parameter!";
      }
      my $newID=$newentity->ID;
      $openhandleIDs->{$newID}->{Handle} = $wrt;
      $openhandleIDs->{$newID}->{Description} =
	$newentity->getParameter("Description");
      $openhandleIDs->{$newID}->{Class} = $classname;
      # This flag will be cleared when the entity sends a CONNECT message.
      $openhandleIDs->{$newID}->{_PendingConnect} = 1;
      # The parent sends the CONNECT message.
      $self->Log("processes", 
		 "New entity created successfully, sending CONNECT message\n");
      $self->sendConnectMessage($newID);
      # Destroy the new entity, we no longer need it, but it still lives
      # in the child.
      undef($newentity);
      # If there are commands to send, send them now.
      if ($cmds && ref($cmds) && ref($cmds) =~ /ARRAY/) {
	my $cmd;
	foreach $cmd (@$cmds) {
	  my ($cmdname, $cmdparams)=split(" ",$cmd, 2);
	  $cmdparams="" if !defined($cmdparams);
	  if ($cmdname) {
	    my $cmdmsg=AAFID::Message->new(TYPE => 'COMMAND',
					   SUBTYPE => uc($cmdname),
					   TO => $newID,
					   DATA => $cmdparams
					  );
	    $self->relayMessage($cmdmsg);
#	    $self->sendMsgTo($cmdmsg, $newID);
	  }
	}
      }
      # Return successfully and happily
      return $self;
    }
    else {
      # In the child...
      # Reinitialize the Comm::Reactor class.
      Comm::Reactor::reset;
      # Close the parent's ends of the pipes.
       close(WRTPAR) 
 	or do { $self->Log("errors", "Error closing in child: $!\n");
 		$@="Error closing in child: $!";
 		return undef;
 	      };
       close(RDRPAR) 
 	or do { $self->Log("errors", "Error closing in child: $!\n");
 		$@="Error closing in child: $!";
 		return undef;
 	      };
      # Then redirect STDOUT and STDIN to the pipes. Make the writing
      # end non-buffered.
      # Obscure perl hackery. See PerlBook, p. 211
      select((select(WRTCHILD), $|=1)[0]);
      open (STDOUT, ">&WRTCHILD") or die "open failed in child: $!";
      select((select(STDOUT), $|=1)[0]);
      open (STDIN, "<&RDRCHILD") or die "open failed in child: $!";
      # Now we call the new entity's run() method. When it returns,
      # we die.
      $newentity->run;
      Comm::Reactor::flush();
      exit(0);
    }
  }
  else { # Error in the fork
    $self->Log("errors", "Fork failed: $!\n");
    $@="Fork failed: $!";
    return undef;
  }
}

=head2 Filters

Filters in the AAFID system are entities that are responsible for obtaining
and providing data to other entities (primarily agents), thus freeing
those entities from having to figure out where or how to get the data.
Filters also perform a filtering function, allowing their client entities
to specify patterns that they want to use to select the data they receive.
The filter then only sends data that matches the patterns to the client
entity. This is described in much more detail in the documentation for
B<AAFID::Filter>.

Of interest here is the mechanism by which a B<ControllerEntity> knows
about filters and handles them. This is described next:

An entity that needs to have a filter available has to declare it in
its C<FiltersNeeded> parameter, which must contain a reference to an
array whose first element is a hash where each key is the filter name,
and the contents of the element is a reference to a hash that contains
the initial pattern to be requested from that filter. The second
element of the array, if it exists, must be the name of a subroutine
that will be invoked as a callback whenever the filter generates
data. The method will be invoked as an instance method of the current
entity with the filter name, filter file handle and the message
received as arguments, this is, the equivalent of:

   $self->callback($fname, $fh, $msg)

where C<callback> is the name of the subroutine, C<$fname> is the name
of the filter (as a string), C<$fh> is the file handle for the filter,
and C<$msg> is the message that was received.

For example, an agent that needs to get data from the
B<NetworkAccesses> filter, that only needs to see the data from
C<telnet> requests, and whose C<processTelnet> method must be called
whenever data comes from that filter, could specify the following in
its C<%PARAMETERS> hash:

  FiltersNeeded => [ {
		      NetworkAccesses => {
					  Service => 'telnetd'
					 }
		     },
		     'processTelnet'
                   ]

If the second argument (the name of the callback subroutine) is not
provided, no callback is associated to the filter, and data from it
will have to be requested manually using the C<readFilter> method.
Notice that at the low level, a callback is established that stores
the messages that come from each filter in the C<FilterMessages>
parameter, so that all that C<readFilter> does is get the next message
from the corresponding element of that parameter.

Before running an entity, the B<ControllerEntity> will check the
C<FiltersNeeded> parameter of the new entity, and if it exists,
will start all the appropriate filters. If a filter has been
already started (for example, because it was also requested by a 
previously loaded entity), then it is not started again, because
a single filter can serve multiple client entities. As each filter
is started, its information is cached in the C<ActiveFilters>
parameter, from where it is taken in case the same filter is requested
again.

The C<ActiveFilters> parameter is a reference to a hash in which each
key is the filter name, and each element is a reference to a hash that
contains the following elements:

=over 4

=item Path

Path to the Unix-domain socket in which the filter listens for
requests from client entities.

=item ID

The ID of the filter that is running.

=back 4

Each filter provides the B<ControllerEntity> with a single piece of
information, which is obtained by reading the C<SocketPath> parameter
of the filter, and that contains the path in which the filter will
set up a Unix-domain socket to listen for requests from client
entities. This information is passed by the B<ControllerEntity> to
the new entity so that it can contact the filter. The information
is passed to the new entity by storing it in its C<FilterPaths>
parameter. This parameter is a reference to a hash, in which each
key is the filter name, and each element contains the path to
the server socket of the filter.

All of the above is done in the C<setupFilters> subroutine, which
receives as an argument an entity, and operates on it.

C<setupFilters> returns a true value if we could get the socket path
for all the filters that the entity requested. This occurs only if no
filters were requested or all of them were already active and thus
their socket paths were cached. Otherwise, even if the filter was
successfully loaded, its socket path will not be known until the
filter sends its CONNECT message. Of course, if a filter could not be
loaded and a request was sent for it, we are in even worse shape.  In
this cases, C<setupFilters> returns C<undef>, which should be
interpreted as "do not run the entity yet". Additionally, information
about the missing filters is stored in the parameter
C<_PendingFilters>, which contains a hash reference where each key is
the entity ID of the entity that has to be started, and each element
is a hash reference that contains the following elements:

=over 4

=item C<Entity>

A reference to the actual entity that is going to be run after all
its filters are in place.

=item C<Filters>

A hash reference where each key corresponds to the class name of a
missing filter.

=back 4

Whenever a CONNECT message is received from a filter, this list is
checked, and any entities whose filters are all present are then
started.

=cut

sub setupFilters {
  my $self=checkref(shift);
  # Get the entity on which we have to operate.
  my $e=shift;
  my $cmds=shift;
  my $eID=$e->ID;
  my $fneeded=$e->getParameter('FiltersNeeded');
  if ( $fneeded ) {
    # Check that it is a hash ref.
    if (ref($fneeded) =~ /^HASH/) {
      # List to keep the list of missing filters.
      my @mf=();
      my $fname;
      foreach $fname (keys %{$fneeded}) {
	$self->Log("processes", "New entity $eID requested filter $fname.\n");
	my $fpath;
	# Initialize the ActiveFilters parameter if necessary
	my $af=$self->getParameter('ActiveFilters', {});
	if (exists($af->{$fname})) {
	  # If the filter is already active, get its path name.
	  $self->Log("processes", "Filter $fname is already active.\n");
	  $fpath=$af->{$fname}->{Path};
	  # Put its path name in the Agent.
	  my $fps=$e->getParameter('FilterPaths', {});
	  $fps->{$fname}=$fpath;
	}
	else {
	  $self->Log("processes", "Filter $fname was not active, loading.\n");
	  my $classname="Filter::$fname";
	  my $result=$self->invoke($classname);
	  if (defined($result)) {
	    # Defined result means either that the load was successful
	    # or that a request was sent for the filter. In any case,
	    # we still have to wait for the CONNECT message from the
	    # filter.
	  }
	  else {
	    $self->Log("errors", "Error loading filter $fname.\n");
	  }
	  # No matter what the result, put the filter as pending.
	  push @mf, $fname;
	}
      }
      # If there were pending filters, create an entry in the
      # _PendingFilters parameter.
      if (@mf) {
	my $pf=$self->getParameter('_PendingFilters', {});
	$self->Log('debug', 
		   "Creating entry for $eID in _PendingFilters with (@mf)\n");
	$pf->{$eID}={};
	# Store the entity and the names of the missing filters.
	$pf->{$eID}->{Entity}=$e;
	$pf->{$eID}->{Filters}={};
	$pf->{$eID}->{Commands}=$cmds;
	foreach (@mf) {
	  $pf->{$eID}->{Filters}->{$_}=1;
	}
	# Return undef so that the entity is not executed.
	return undef;
      }
      else {
	# We are all clear to go! A true return value should cause
	# the entity to be executed (its 'run' method called).
	$self->Log('processes', "All filters ready.\n");
	return $self;
      }
    }
    else {
      $self->Log("errors", 
		 "FiltersNeeded parameter is not a hash reference, ".
		 "I will not do any filter initialization\n");
      return undef;
    }
  }
  else {
    # If the entity did not request filters, we are by definition
    # successful.
    $self->Log("processes", "The new entity $eID did not request filters.\n");
    return $self;
  }
}

=pod

In order to recognize when a filter starts up, we have to add functionality
to the C<message_CONNECT> subroutine. When a filter starts up, it sends
a CONNECT message to its parent entity, as any other entity, but its
subtype, instead of being CHILD, is FILTER. The DATA field of this message
will contain the class name, the socket path name, and the description of
the filter. From this field is from where we take the socket path name
to put it in the C<ActiveFilters> parameter. After that, we look in
the C<_PendingFilters> parameter and delete the filter we just got from
all the entries. We start those entities which now have all their
required filters.

=cut

sub message_CONNECT {
  my $self=checkref(shift);
  my $msg=checkref(shift, "AAFID::Message");
  # First, call the method in the superclass.
  my $result=$self->SUPER::message_CONNECT($msg);
  if ($result) {
    # Some error happened, just return it.
    return $result;
  }
  # Now, if it is a filter, do our work.
  if (uc($msg->{SUBTYPE}) eq "FILTER") {
    $self->Log("processes", "Got connection from a filter.\n");
    # First, resplit the DATA field.
    my ($fclass, $fpath, $fdesc)=split(/ /, $msg->{DATA}, 3);
    # Store the description correctly.
    my $openhandles=$self->getParameter('OpenHandleIDs');
    my $entityid=$msg->{FROM};
    my $entityrecord=$openhandles->{$entityid};
    $entityrecord->{Description} = $fdesc;
    # Store the path name in the ActiveFilters parameter.
    my $af=$self->getParameter('ActiveFilters', {});
    # The index to the ActiveFilters parameter is just the filter name,
    # without the fully qualified class name, so we eliminate that part.
    $fclass =~ s/.*:://;
    $self->Log("debug", "Filter $fclass reports socket $fpath, storing.\n");
    $af->{$fclass}->{Path}=$fpath;
    $af->{$fclass}->{ID}=$entityid;
    # Now we go over the _PendingFilters list, eliminating the filter
    # we just got from the entries, and storing the appropriate pathname
    # in the entities' FilterPaths parameters. 
    # Any entities for which this one was
    # the last missing filter are executed.
    my $pf=$self->getParameter('_PendingFilters', {});
    my $k;
    foreach $k (keys %$pf) {
      my $entry=$pf->{$k};
      if ($entry->{Filters}->{$fclass}) {
	$self->Log("debug", "Updating pending filters for entity $k\n");
	delete $entry->{Filters}->{$fclass};
	# Update the path directly in the entity.
	$entry->{Entity}->getParameter('FilterPaths', {})->{$fclass}=$fpath;
      }
      if (!%{$entry->{Filters}}) {
	$self->Log("debug", 
		   "This was the last missing filter for $k, " .
		   "so I'm running it now.\n");
	my $e=$entry->{Entity};
	my $cmds=$entry->{Commands};
	delete $entry->{Entity};
	delete $pf->{$k};
	if ($self->_fork_and_run($e, ref($e), $cmds)) {
	  # Things went ok.
	}
	else {
	  # Error.
	  return newErrorMsg({Message => "Error starting entity $k"});
	}
      }
    }
  }
  return undef;
}

=head2 The START command

Very related to the C<invoke> function, the new B<START> command takes
an argument called C<Class> and creates a new instance of the
specified class or file. Essentially, it calls C<invoke> with its
argument as the class locator.

Multiple classes to be starte may also be specified by the C<Classes>
parameter, which is considered to be a string containing a 
space-or-comma-separated list of classes to start. Both the C<Class>
and the C<Classes> parameters may appear at the same time, and all
the specified classes are started.

If the C<Commands> parameter is given, it must contain an array
reference that contains a list of strings of the form 
"COMMAND params", which will be sent to the new entities upon
startup.

=cut

sub command_START {
  my $self=checkref(shift);
  my ($msg, %params)=@_;
  my $code;
  my $codefname;
  my $result;
  my $classname=$params{Class};
  my @classes;
  my $cmds=$params{Commands};
  if ($params{Classes}) {
    @classes=splitList($params{Classes});
  }
  if ($classname) {
    push @classes, $classname;
  }
  foreach $classname (@classes) {
    $result=$self->invoke($classname, $cmds);
    if ($result) {
      next;
    }
    else {
      $@ =~ s/\s+/ /g;
      return {Error => "$@. Some classes may not have been started." };
    }
  }
  return undef;
}

=pod

The path where entities specified without a full path are searched for
can be augmented by the C<ADDPATH> command. It receives an argument
called C<Path> which contains a comma-separated list of directory names,
which may be absolute or relative (these are interpreted with respect
to the current working directory of the ControllerEntity).

=cut

sub command_ADDPATH {
  my $self=checkref(shift);
  my ($msg, %params)=@_;
  my $cwd=cwd();
  if ($params{Path}) {
    $self->Log("debug", "\@INC before updating: @INC\n");
    my @path=split(/[, :]+/, $params{Path});
    unshift @INC, map {(m!^/!)?$_:"$cwd/$_"} @path;
    $self->Log("debug", "\@INC after updating: @INC\n");
  }
  return undef;
}

=head1 Loading modules and looking for missing code

When a new module load (for example, for instantiating a new entity) is
requested, we have to check if we already have the module loaded. If so,
there is nothing else to do. However, if it is not there, we have to
try to load it first from the local disk. If it is not there, we have
to request it up using a NEEDMODULE message. The response to this command
should be a NEWMODULE message, which will include the code requested. 
Furthermore, when evaluating this new code, new dependencies may be
discovered, and new code may have to be requested. This will have to
be repeated as many times as necessary, keeping track of which loads
are being waited on, and which ones have to be attempted after each one
is successful. We do not try to parse Perl code here. We simply try to
evaluate it, and if it produces a "Can't locate ..." error, we extract
the name of the missing class, and proceed from there.

The C<loadmodule> subroutine magically takes care of almost all of the
above. It helps itself in the task by using parameters C<_PendingLoads>,
C<_PendingRuns> and C<_LoadChain>. See the "Standard ControllerEntity
Parameters" section for a full description of them.

C<Loadmodule> can return three different values, depending on the result
of the load:

=over 4

=item C<$self>

The load was successful.

=item -1

The load was not successful, but because a request was sent, which should
be fulfilled later.

=item C<undef>

An error occured.

=back 4

=cut

sub loadmodule {
  my $self=checkref(shift);
  my $class=shift;	# The class name that is being requested.
  my $fname;		# The filename from which it will be loaded.

  if (!$class) {
    $self->Log("processes", "Null class specification given to loadmodule\n");
    $@="Null class specification given to loadmodule";
    return undef;
  }

  # Do something only if the class hasn't already been loaded.
  if ($self->_needtoload($class)) {
    $self->Log("debug", "Need to load $class.\n");

    # First, try to load it using use or require, depending on whether
    # we were given a class name or a file path.
    if ($class =~ m!^/!) {
      # We were given a full filename. Obtain the class name.
      $fname=$class;
      my $class=basename($class, '.pl', '.pm');
      $self->Log("debug", "Trying to load class $class from file $fname.\n");
      eval "require '$fname'; import $class";
    }
    else {
      # We were given the class name, just 'use' it.
      # Get the filename anyway.
      $fname=$class;
      # Convert :: to /, add ".pm"
      $fname =~ s!::!/!g;
      $fname .= ".pm" if !($fname =~ /\.pm$/);
      $self->Log("debug", "Trying to load module $class.\n");
      eval "use $class";
    }
    
    if ($@) {
      # The first attempt failed. Now see if we had a path for
      # the class already and try to load from there.

      # Delete from %INC just in case the previous one failed but because
      # of a missing include, in which case it gets put in %INC anyway.
      delete $INC{$fname};

      # See parameters reference at the end for description of _PendingLoads.
      my $pl=$self->getParameter('_PendingLoads', {});
      my $fname2=$pl->{$class};

      if ($fname2) {
	# If we have a filename stored, we only need to load it.
	$fname=$fname2;
	$self->Log("debug", "Trying to load class $class from file $fname.\n");
	eval "require '$fname'; import $class;";
      }
    }

    # No matter how we attempted to load the class, check the result
    # and proceed correspondingly.
    if ($@) {
      $self->_loadmodule_error($class, $fname, $@);
    }
    else {
      $self->_loadmodule_success($class, $fname);
    }
  }
  else {
    # Already loaded
    $self->Log("debug", "$class was already loaded, not loading again.\n");
    # Need to do housekeeping, check if the class needs to be run,
    # etc.
    $self->_loadmodule_success($class, undef);
    return $self;
  }
}

=pod

The C<_loadmodule_error> subroutine handles errors that occur when
requesting a file that needs to be loaded. It checks the kind of error
that occurred. Missing-file errors can be handled by requesting the
missing files. Other errors are simply displayed, we do not know how
to recover from those.

=cut

sub _loadmodule_error {
  my $self=checkref(shift);
  my ($class, $fname, $error)=@_;
  # Eliminate newlines, tabs and other extraneous blank spaces.
  $error =~ s/\s+/ /g;
  # We have to delete the filename from %INC because Perl inserts it
  # there even if the load failed, and then _needtoload will return
  # "false" the next time we try.
  delete $INC{$fname};

  if ($error =~ /Can\'t locate\s+(\S+)\s+in\s+\@INC/) {
    my $missing=$1;
    $self->Log("debug", "Error when loading $fname: $error\n");
    if ($missing eq $fname) {
      # Missing file is the one we tried to load, so request it.
      $self->Log("debug", 
		 "Class $class not found in local disk. Requesting it.\n");
      $self->requestModule($class);
      $@="Class $class not found locally, sent request for it.";
      return -1;
    }
    else {
      # The class we tried to load requires some other class that
      # we don't have, so we have to request it and postpone
      # the loading of $fname until we receive it.
      $missing =~ s/\.p[lm]$//;
      # Convert slashes back to double colons.
      $missing =~ s!/!::!g;
      $self->Log("debug", "$class needs $missing. Trying to load it.\n");
      my $missingresult=$self->loadmodule($missing);
      if ($missingresult==-1) {
	# This should be the normal case, the load failed with the
	# class being requested. So we store the information that will
	# allow us to continue with the processing of $class as soon
	# as we receive $missing.
	# See param reference at the end for description of _LoadChain
	if (!($self->getParameter('_LoadChain'))) {
	  $self->setParameter(_LoadChain => {});
	}
	$self->Log("debug", "Setting chain from $missing to $class.\n");
	$self->getParameter('_LoadChain')->{$missing}=$class;
	$@="Waiting for missing dependency $missing";
	return -1;
      }
      elsif (defined $missingresult) {
	# For some weird reason, the load of $missing succeeded after it
	# had failed, so we try $class again.
	$self->Log("debug", "Load of $missing succeeded. Retrying $class.\n");
	return $self->loadmodule($class);
      }
      else {	# Error
	return undef;
      }
    }
  }
  else {
    # Some other error.
    $self->Log("errors", "Error loading $class: $error\n");
    # We do not delete the file, we want a permanent cache for now.
    delete $self->getParameter('_PendingLoads')->{$class};
    $@="Error loading $fname: $error";
    return undef;
  }
}

=pod

The C<_loadmodule_success> subroutine takes care of the housekeeping
tasks that need to be done when the load is successful. These include 
instantiating and running the class if it was a pending entity that had
to be run, or proceeding with loading other classes that needed the one
we just loaded and that were pending.

=cut

sub _loadmodule_success {
  my $self=checkref(shift);
  my ($class, $fname)=@_;
  
  # Load successful. Proceed with chains and stuff.
  $self->Log("debug", "Load of $class successful.\n");
  $self->_putincache($class);
  if (!$self->getParameter('_PendingLoads')) {
    $self->setParameter(_PendingLoads => {});
  }
  delete $self->getParameter('_PendingLoads')->{$class};
  if (!$self->getParameter('_PendingRuns')) {
    $self->setParameter(_PendingRuns => {});
  }
  if ($self->getParameter('_PendingRuns')->{$class}) {
    # What we just loaded is an entity that needs to be run.
    $self->Log("processes", "Instantiating and running a $class\n");
    my $cmds=$self->getParameter('_PendingCmds', [])->{$class};
    if (!$self->_instantiate_and_run($class, $cmds)) {
      $self->Log("errors", "Error running a $class: $@\n");
      $@="Error running a $class: $@";
      return undef;
    }
    else {
      # If it ran correctly, delete it from the pending table.
      delete $self->getParameter('_PendingRuns')->{$class};
    }
  }
  if (!$self->getParameter('_LoadChain')) {
    $self->setParameter(_LoadChain => {});
  }
  my $next=$self->getParameter('_LoadChain')->{$class};
  if ($next) {
    # What we just loaded was was a prerrequisite for something else,
    # so go on with that.
    $self->Log("processes", "Going for next in chain: $next.\n");
    $self->loadmodule($next);
  }
  # We don't care about the result of loading the next one, we
  # were successful.
  return $self;
}

=pod

When the C<loadmodule> subroutine needs to make a request for code we
do not have, the request has to be sent "Up" in the form of a
C<NEEDMODULE> message, which has the following format:

   NEEDMODULE classname

This message is sent up by the C<requestModule> subroutine.

=cut

sub requestModule {
  my $self=checkref(shift);
  my $class=shift;
  if (!$class) {
    $self->Log("errors", "Null classname provided to requestModule.\n");
    return;
  }
  my $msg=$self->newMsg(TYPE	=> "NEEDMODULE",
			SUBTYPE	=> $class,
		       );
  $self->sendReport($msg);
  return $self;
}

=pod

The answer to a C<NEEDMODULE> message will be a C<NEWMODULE> message,
which will contain the class name in the SUBTYPE field, and a unique
identifier in the DATA field. The following lines will contain the
code itself, which will be terminated by a line containing only the
identifier. Thus, we need to be able to respond to these messages.

=cut

sub message_NEWMODULE {
  my $self=checkref(shift);
  my $msg=checkref(shift, "AAFID::Message");
  my $class=$msg->{SUBTYPE};
  my $fname=$class;
  $fname =~ s@::@/@g;
  my $codeid=$msg->{DATA};
  if (!defined($codeid) || !$codeid) {
    $codeid="";
    return newErrorMsg({Error => "Invalid empty code id provided"});
  }
  my $msgfrom=$msg->{_FROMHANDLE};
  # TODO: Which one is the standard environment variable for temporary
  # directory?
  # TODO: Make the code directory configurable and secure.
  my $codefname=($self->getParameter('CodeCacheDir'))."/${fname}.pm";
  # Create the directory for the class, if needed.
  my $codedirname=dirname($codefname);
  if (! -d $codedirname) {
    mkpath($codedirname, 0, 0700);
    if (! -d $codedirname) {
      return newErrorMsg({Error => "Could not create directory $codedirname"});
    }
  }
  my $sel=$self->getParameter('OpenHandles');
  my $codefile=IO::File->new(">$codefname") or
    return newErrorMsg({Error => 
			"Could not open file for writing: $codefname: $!"});
  $self->setParameter(_TmpCodeFileHandle => $codefile );
  $self->Log("processes", "Reading code for $class, Code id '$codeid'\n");
  $self->Log("debug", "Temporarily changing callback for handle $msgfrom.\n");
  # First add a dummy callback just to get the value of $prevcb, which
  # is used in the definition of the real callback.
  my $prevcb=Comm::Reactor::add_handle($msgfrom, sub { ; });
  Comm::Reactor::add_handle($msgfrom,
       sub {
	 my ($fh, $msg)=@_;
	 my $codefh=$self->getParameter('_TmpCodeFileHandle');
	 $self->Log("debug", "Got code line: $msg\n");
	 if ($msg =~ /^\s*$codeid\s*$/) {
	   $codefh->close or 
	     return newErrorMsg({Error => "Error closing $codefname: $!"});
	   # Remove the temporary parameter.
	   $self->setParameter(_TmpCodeFileHandle => undef);
	   $self->Log("processes", "Got all code, wrote to $codefname.\n");
	   if (!($self->getParameter('_PendingLoads'))) {
	     $self->setParameter(_PendingLoads => {});
	   }
	   $self->getParameter('_PendingLoads')->{$class}=$codefname;
	   my $loadresult=$self->loadmodule($class);
	   if (!defined($loadresult)) {
	     $self->Log("processes", "loadmodule produced: $@\n");
	   }
	   else {
	     $self->Log("processes", "Loaded $class successfully\n");
	   }
	   # Remove the file handle.
	   # Return the callback to its original value.
	   Comm::Reactor::add_handle($fh, $prevcb);
	 }
	 else {
	   chomp $msg;
	   print $codefh "$msg\n" or 
	     return newErrorMsg({Error => "Error writing to code file: $!"});
	 }
       });
  # Return. The callbacks will handle the rest.
  return undef;
}

=head1 Stopping an entity

The KILL command takes an argument called C<Entities>, which contains a 
comma-or-space-separated list of entitiy IDs. All those entities will be
stopped by sending them a STOP message. The entity is not removed from
the tables at this point, this is done when the DISCONNECT message is
received from the entity.

=cut

sub command_KILL {
  my $self=checkref(shift);
  my ($msg, %params)=@_;
  if ($params{Entities}) {
    $self->Log("processes", "Got a request to kill $params{Entities}\n");
    my @entities=split(/[, ]+/, $params{Entities});
    my $openhandles=$self->getParameter('OpenHandleIDs');
    my $ent;
    my $stopmsg=$self->newMsg(TYPE => "STOP");
    foreach $ent (@entities) {
      if (exists($openhandles->{$ent})) {
	$self->Log("debug", "Sending STOP message to entity $ent\n");
	$self->sendMsgTo($stopmsg, $ent);
      }
      else {
	$self->Log("errors", "I don't know entity $ent, cannot stop her\n");
      }
    }
  }
  return undef;
}

=head1 Querying existing entities.

The LISTENTITIES command requests a list of all the currently registered
subentities. It is returned in a COMMAND RESULT message that contains an
C<Entities> field with a comma-and-space-separated list of all the
currently registered entity identifiers.

=cut

sub command_LISTENTITIES {
  my $self=checkref(shift);
  my ($msg, %params)=@_;
  my $openhandles=$self->getParameter('OpenHandleIDs');
  my @entids=grep {!$openhandles->{$_}->{_isUp}} keys(%$openhandles);
  return {Entities => join(",", @entids), NumEntities => scalar(@entids)};
}

=head1 Stopping and cleaning up

When a STOP command is received or for some reason it is time to finish
the time of the ControllerEntity, it cannot be simply terminated. Instead
it has to send STOP messages to all the entities it is controlling, and
wait for them to send their respective DISCONNECT messages before actually
terminating.

This is what the C<Cleanup> method does. The C<_Finishing> internal
parameter is set so that C<message_DISCONNECT> can take the appropriate
actions when the last subentity disappears. We also redefine _cleanup
to avoid exiting immediately when a cleanup is called. 

However, we have to check if we can exit immediately. This is the case when
there are no subentities. For this, if C<Cleanup> returns a true value,
we continue on with our life, but if it returns a false value, we
exit immediately.

=cut

sub Cleanup {
  my $self=checkref(shift);
  my $desc=shift;		# Optional description
  my $stopmsg=$self->newMsg(TYPE => "STOP");
  $self->setParameter(_Finishing => 1);
  $self->setParameter(_FinishDescription => $desc);
  if ($self->_sendToAllSubEntities($stopmsg)==0) {
    return undef;
  }
  else {
    # Hack - Fix - TODO
    # TODO: This "return undef" is a hack because it was dumping core under
    # Linux. This seemed to fix it some, but we really need to figure out
    # its real cause.
    #    return undef;
    return $self;
  }
}

sub _cleanup {
  my $self=checkref(shift);
  my $desc=shift;
  
  if ($self->Cleanup($desc)) {
    return $self;
  }
  else {
    $self->_realstop;
  }
}

=pod

As it can be seen above, the standard cleanup routines do not really
terminate the program in a C<ControllerEntity>. So we add two new
routines.  The C<_realstop> method does the final termination of the
entity, but before that it calls C<realstop>, which may be overriden
by a subclass implementer to provide entity-specific final
termination.

=cut

sub _realstop {
  my $self=checkref(shift);
  $self->realstop;
  $self->Log("processes", "Terminating. Good bye.\n");
  $self->sendDisconnectMessage($self->getParameter('_FinishDescription'));
  # Remove temporal code directory.
  my $tmpdir=$self->getParameter('CodeCacheDir');
  if ($tmpdir && -d $tmpdir) {
    $self->Log('debug', "Removing temporary directory $tmpdir.\n");
    rmtree($tmpdir, 0, 1);
  }
  exit(0);
}

sub realstop {
  my $self=checkref(shift);
  # Insert your final-final-final termination code here.
}

=pod

Related to this, we need to redefine the STOP command to only call
the C<_cleanup> method, but not exit, as this comes later when all the
DISCONNECT messages have been received.

=cut 

sub command_STOP {
  my $self=checkref(shift);
  # Don't even look at the arguments.
  $self->_cleanup("STOP command or message");
  return undef;
}

=pod

=head1 Receiving messages from entities

When an entity finishes its existence, it sends a DISCONNECT message up.
Thus, the controller entity has to be able to receive this message and
react by removing the entity from its tables. Additionally, if the
C<_Finishing> parameter is set, then after each DISCONNECT message, we
will check if all controlled entities have disappeared, and if so,
disappear ourselves.

=cut 

sub message_DISCONNECT {
  my $self=checkref(shift);
  my $msg=checkref(shift, "AAFID::Message");
  my $openhandles=$self->getParameter('OpenHandleIDs');
  my $selectobject=$self->getParameter('OpenHandles');
  my $who=$msg->{FROM};
  $self->Log("processes", "Got DISCONNECT message from $who\n");
  if (exists($openhandles->{$who})) {
    # We only close the handle if the entity is not above, because
    # we would close STDOUT and do funny things.
    unless ($openhandles->{$who}->{_isUp}) {
      # Close the handle and remove from the tables.
      $selectobject->remove($openhandles->{$who}->{Handle});
      $openhandles->{$who}->{Handle}->close;
    }
#    else {
#      $self->Log("processes", 
#		 "The DISCONNECT comes from above. Strange. Doing nothing\n");
#    }
    delete $openhandles->{$who}->{Handle};
    delete $openhandles->{$who};
    # If we are waiting for the children to finish before dying, check.
    if ($self->getParameter('_Finishing')) {
      my @pending=grep {!$openhandles->{$_}->{_isUp}} keys %$openhandles;
      if ($#pending<0) {
	$self->_realstop;
      }
    }
  }
  else {
    $self->Log("processes", "But I never heard from her, do nothing\n");
  }
  return undef;
}

=pod

The default behavior in this class for unknown messages and commands
is the following: if the message/command comes from one of the
subentities (from "below"), then it is passed up. If it comes from
"above" (from standard input), its "TO" field is checked. If it is the
name of an existing subentity (registered in the C<OpenHandleIDs>
parameter), the message is sent to that entity.  Otherwise, it is sent
to all the registered subentities.

=cut

sub message_NoSuchMessageType {
  my $self=checkref(shift);
  my $msg=checkref(shift, "AAFID::Message");
  my $openhandles=$self->getParameter('OpenHandleIDs');
  if ($msg->{_FROMSTDIN}) {
    if (exists($openhandles->{$msg->{TO}})) {
      unless ($openhandles->{$msg->{TO}}->{_isUp}) {
	$self->Log("messages", 
		   "Got message for $msg->{TO}, relaying: " .
		   $msg->toString."\n");
	$self->sendMsgTo($msg, $msg->{TO});
      }
    }
    else {
      $self->Log("messages", 
		 "Got a message with unknown destination, sending to ".
		 "all children: ".$msg->toString."\n");
      $self->_sendToAllSubEntities($msg);
    }
  }
  else {
    $self->Log("messages", 
	       "Got an unknown message from $msg->{FROM}, sending up: " .
	       $msg->toString."\n");
    $self->sendReport($msg);
  }
  return undef;
}

sub command_NoSuchCommand {
  my $self=checkref(shift);
  my ($msg,%params)=@_;
  my $openhandles=$self->getParameter('OpenHandleIDs');
  $self->Log("debug", "Entering ControllerEntity::command_NoSuchCommand\n");
  if ($msg->{_FROMSTDIN}) {
    if (exists($openhandles->{$msg->{TO}})) {
      unless ($openhandles->{$msg->{TO}}->{_isUp}) {
	$self->Log("messages", 
		   "Got command for $msg->{TO}, relaying: " . 
		   $msg->toString."\n");
	$self->sendMsgTo($msg, $msg->{TO});
      }
    }
    else {
      $self->Log("messages", 
		 "Got a command from above with unknown destination, " .
		 "sending to all children: " . $msg->toString."\n");
      $self->_sendToAllSubEntities($msg);
    }
  }
  else {
    $self->Log("messages", 
	       "Got an unknown command from $msg->{FROM}, sending up: " .
	       $msg->toString."\n");
    $self->sendReport($msg);
  }
  return undef;
}

=head1 Communicating with all subentities

Sometimes (for example, when about to finish, or when relaying messages),
a message has to be sent to all the currently registered subentities. This
is what the C<_sendToAllSubEntities> method does. It returns the number
of message sent.

=cut

sub _sendToAllSubEntities {
  my $self=checkref(shift);
  my $msg=checkref(shift, "AAFID::Message");
  my $openhandles=$self->getParameter('OpenHandleIDs');
  my $count=0;
  if (!$openhandles) {
    croak $self->ID.": Panic: I don't have my OpenHandleIDs parameter";
  }
  foreach (keys %$openhandles) {
    unless ($openhandles->{$_}->{_isUp}) {
      # This action is logged by sendMsgTo, no need here.
      $self->sendMsgTo($msg, $_);
      $count++;
    }
  }
  return $count;
}

=head1 Standard ControllerEntity parameters

=over 4

=item LoadedClasses

A reference to a hash that contains the names of all the classes that
have been already loaded. If a new entity of that class is requested, it
will not be loaded again, just instantiated.

=item _Finishing

True when the ControllerEntity is in the process of finishing up. This 
happens when the entity has received a STOP message, sent STOPs to all
its subentities, and is waiting for all of them to confirm with a 
DISCONNECT.

=item _FinishDescription

The description of the termination, to be used in the DISCONNECT
message that will be sent up when the entity terminates.

=item _PendingLoads

Reference to a hash whose keys are the names of classes whose code has
been received but not yet loaded, and the elements contain the
filename in which the code is stored.  Used by C<loadmodule>.

=item _PendingRuns

Reference to a hash whose keys are class names that are pending load,
but that when they are finally loaded successfully, need to be
instantiated and run as entities.

=item _LoadChain

If code is received but it is discovered that it needs yet another
module or piece of code that is missing, the new missing module is
requested, and after it is received, loading of the original module
has to continue. This parameter is a reference to a hash whose keys
are the dependencies, and their contents is the class that depends on
them.  For example, if the code for class "Foo" is received, but when
trying to load it we discover that it needs class "Bar", which we don
not have, we make a request for class "Bar", and set element
C<_LoadChain->{Bar}="Foo">. This way, when we receive the code for
"Bar" and successfully load it, we know that we have to continue
trying to load "Foo". Used in subroutine C<loadmodule>.

=item CodeCacheDir

Directory where packages that are sent from above are stored, for
future use.

=item ActiveFilters

A reference to a hash in which each key is the filter name, and each
element is a reference to a hash that contains the following elements:

=over 4

=item Path

Path to the Unix-domain socket in which the filter listens for
requests from client entities.

=item ID

The ID of the filter that is running.

=back 4

=item _PendingFilters

Contains information about entities that still need some filters to
become active in order to be able to run. This parameter contains a
hash reference where each key is the entity ID of the entity that has
to be started, and each element is a hash reference that contains the
following elements:

=over 4

=item C<Entity>

A reference to the actual entity that is going to be run after all
its filters are in place.

=item C<Filters>

A hash reference where each key corresponds to the class name of a
missing filter.

=item C<_TmpCodeFileHandle>

Temporary parameter that points to the file handle for the code that
is being received as part of a NEWMODULE command. See
C<command_NEWMODULE>.

=back 4

Whenever a CONNECT message is received from a filter, this list is
checked, and any entities whose filters are all present are then
started.

=back 4

=cut

_EndOfEntity;

#
# $Log: ControllerEntity.pm,v $
# Revision 1.28  1999/09/03 17:08:53  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.27  1999/08/31 07:10:41  zamboni
# Made TmpCodeBaseDir default to the value of TmpDir.
#
# Revision 1.26  1999/06/28 21:22:03  zamboni
# Merged with a07-port-to-linux
#
# Revision 1.25.2.1  1999/06/28 18:27:35  zamboni
# Made a change to make it work under Linux. Then returned it to its
# original condition, but with the commented change for future
# reference.
#
# Revision 1.25  1999/06/08 05:01:53  zamboni
# Merged branch a06-raw-data-collection into main trunk
#
# Revision 1.24.2.1  1999/06/07 15:56:45  zamboni
# - Added the capability of sending commands to an agent immediately after
#   starting it.
# - Made "LISTENTITIES" return also a "NumEntities" parameter.
#
# Revision 1.24  1999/04/01 02:31:13  zamboni
# - Made changes to allow disconnections from STDIN and then later
#   reattachments. Doesn't really work yet.
#
# Revision 1.23  1999/03/29 22:33:24  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.22.4.1  1999/03/29 16:10:01  zamboni
# - Modified message_NEWMODULE to use the new event-based communication
#   modules.  Previously, this routine read directly from the file
#   handle on which the message came to get the code for the
#   module. Now, it changes the callback for that handle to a new
#   function that stores the lines of code, and returns the callback to
#   its normal value when all the code has been received.
# - Modified _fork_and_run to add the corresponding callback to get
#   messages from the new entity.
# - Added description of the _TmpCodeFileHandle parameter.
# - Fixed a problem in loadmodule that was preventing it from correctly
#   reloading a class when it had previously failed due to a missing class,
#   after the missing class had been received.
# - Seems that most of it (except filters) work with the new communication
#   modules.
# - Added a call to Comm::Reactor::reset in the child after spawning a new
#   entity. This was preventing filters from working properly.
# - Updated to use the new syntax of the FiltersNeeded parameter, which
#   allows to specify a callback for each filter.
# - Miscellaneous small changes to setupFilters.
#
# Revision 1.22  1998/09/09 03:00:06  zamboni
# * ControllerEntity.pm:
#   - Made the temporary source code directory configurable through
#     environment variables (TMPDIR, TEMPDIR, TMP, TEMP) and through
#     the configuration parameters TmpSourceDir and TmpSourcDirname.
#   - Made it create appropriate directories within the temporary
#     source code directory.
#   - Added multiple class support to START command.
#   - Removed LIST_ENTITIES command, left only LISTENTITIES.
#   - Made it remove the temporary code directory.
#
# Revision 1.21  1998/09/07 17:35:19  zamboni
# Added support for filters!
#
# * Monitor.pm: Replaced all uses of AAFID::Message->new by uses
#   of $self->newMsg.
#
# * Filter.pm: Cleaned up the code and the comments, and added some
#   extra features, such as default versions of makefield and
#   makeline that make it easier to define new filters if they read
#   data where the fields are space-separated. Also, added some
#   error checks here and there.
#
# * Entity.pm: Added filter support
#   - Added descriptions for FiltersNeeded and FilterPaths parameters.
#   - Modified getParameter so that if a second argument is given, it is
#     used to initialize the parameter in case it is undefined.
#   - Added subroutines connectFilters, newFilterClientSocket,
#     setFilterPattern
#   - Added subroutine newMsg.
#
# * ControllerEntity.pm: Added filter support.
#   - Some general cleanup (removing stray commented code, etc.)
#   - Rewrote some sections for clarity (in invoke, _invoke,
#     _instantiate_and_run)
#   - Modularized loadmodule. Created new subroutines:
#       _loadmodule_error
#       _loadmodule_success
#   - Modularized _instantiate_and_run, creating new subroutine
#       _fork_and_run.
#   - Added the setupFilters subroutine, which takes care of loading
#     all the filters required by an entity. It is complemented by
#     an augmented message_CONNECT, which detects when a filter is
#     connecting and does the necessary updating.
#
# * Comm.pm: Removed an annoying debug message from bufferInput.
#
# * Agent.pm: Added code to contact the filters.
#
# Revision 1.20  1998/08/27 16:38:30  zamboni
# Moved log to the end of the file.
#
# Revision 1.19  1998/08/27 16:37:51  zamboni
# Made it work with the new nextLine function in message_NEWMODULE, instead
# of using perl's <$fh> syntax to read the lines, to avoid using I/O
# buffering.
#
# Revision 1.18  1998/06/29 20:11:23  zamboni
# Added copyright message
#
# Revision 1.17  1998/06/26 21:24:50  zamboni
# - Added "use AAFID::Config"
# - Made command_ADDPATH split its argument also using colons (apart from
#   commas or spaces, which it had before).
# - Removed registration and activation of log categories, because that is
#   now done by Entity.pm.
# - Made the directory for temporary source files a configurable parameter
#   called TmpSourceDir.
# - Made command_ADDPATH also recognize colon-separated paths.
#
# Revision 1.16  1998/05/15 00:12:39  zamboni
# Made the temporary code directory include the process ID to make it
# unique to each run. This is not completely what I intend, I think it
# should be some kind of semi-permanent storage, but have to work
# more on this.
#
# Revision 1.15  1998/05/03 03:35:07  zamboni
# - In _instantiate_and_run, made the parent process destroy its copy of
#   $newentity after extracting the information it needs. The new entity
#   keeps existing in the child, which runs it.
#
# Revision 1.14  1998/03/17 06:40:43  zamboni
# - Changed the name of the MyCodeDir parameter to CodeCacheDir, because it
#   is more descriptive, and because I want to leave the "My" prefix free
#   for user-defined parameters.
# - Changed cleanup to Cleanup to be consistent with Init.
#
# Revision 1.13  1998/03/16 05:48:32  zamboni
# - Did some general cleanup and documentation.
# - Corrected a problem in loadmodule. If the class to load was there, but
#   a required class was not, the NEEDMODULE was done correctly, but after
#   receiving it, the original requested class was not instantiated.
# - Corrected another problem in loadmodule: If the following happened:
#   1. A START of class xxx was requested.
#   2. Class xxx did not exist locally, and was requested up.
#   3. Class xxx was provided.
#   4. Upon loading, discover that xxx needed yyy, which was not present,
#      and thus requested.
#   5. yyy was provided.
#
#   then upon trying to load xxx again after receiving yyy, it would not
#   be correctly recognized as an AAFID::Entity. It boiled down to an error
#   in deleteing xxx from the %INC cache after loading it.
#
# Revision 1.12  1998/03/16 02:51:08  zamboni
# - Corrected an error in loadmodule that caused a crash when the _PendingRuns
#   parameter had not been previously defined.
#
# Revision 1.11  1998/03/14 05:52:16  zamboni
# - Modified _needtoload to also look if the package is registered in %INC.
# - Added loadmodule subroutine.
# - Added documentation for _PendingLoads, _PendingRuns, _LoadChain.
# - Added message_NEWMODULE and requestModule.
# - Made changes to _invoke to use loadmodule to (request if necessary and) load
#   the module provided. Also, created a separate routine _instantiate_and_run,
#   which after a module is loaded, creates an instance and runs it. It is also
#   used by the new version of _invoke.
# - Made it create a temporary directory where the code for all package received
#   through the NEWMODULE command will be stored, for future use by other
#   received classes that 'use' them.
# - Debugged the request and reception and caching and handling in general of
#   missing code. There are some rough edges, marked with TODO in the code, but
#   it seems to work in general.
#
# Revision 1.10  1998/03/13 17:01:10  zamboni
# - Modified command_START to intepret the Code parameter as containing an
#   identifier, and then reading all successive lines as code, until the
#   identifier is found, at which point the end of the code is considered.
# - Also made it write all the code received to a file, and then 'requre' it
#   from there, because trying to evaluate it directly from the variable
#   produced a weird error.
# - Made $VERSION keep up with RCS revision.
# - Added %PARAMETERS.
#
# Revision 1.9  1998/03/06 22:18:24  zamboni
# Made some modifications to again try to transfer the code. Doesn't work yet.
#
# Revision 1.8  1998/03/06 16:12:37  zamboni
# - Added to command_START code to interpret a Code parameter, if it is passed,
#   and added to invoke the ability to detect if that parameter is present,
#   and if so eval it instead of looking for the file. However, it doesn't
#   work yet, I have to keep looking at it. For now, we will always use local
#   file loading.
#
# Revision 1.7  1998/03/06 07:10:08  zamboni
# - Removed the checking for remote locators of the form host:class from
#   invoke. This was moved to Monitor::invoke.
# - Added "use strict"
# - Corrected a number of things pointed out by strict.
# - Removed a Log action in _sendToAllSubEntities that results in a duplicate
#   message being generated.
# - Corrected an error in command_DISCONNECT that made the entity finish
#   when all existing subentities disconnected, regardless of whether we
#   were in "finishing" state.
#
# Revision 1.6  1998/03/05 07:06:25  zamboni
# - Corrected a logic error in _sendToAllSubEntities that made it count
#   handles that did not have to be counted (in particular, it was counting
#   "up" handles, even though it was not sending them the message).
#
# Revision 1.5  1998/03/05 03:32:09  zamboni
# - Added checks when a STOP is issued to see if we have to exit immediately.
#   This is the case when there are no subentities active.
#
# Revision 1.4  1998/03/04 22:08:11  zamboni
# - Made command_NoSuchCommand and message_NoSuchMessageType return undef,
#   because they were returning spurious values.
# - Added _sendToAllSubEntities method, which is used so far in cleanup,
#   command_NoSuchCommand and message_NoSuchMessageType.
# - Changed command_STOP, as well as cleanup and _cleanup, to not terminate
#   immediately, but only send a STOP message to all the subentities, and
#   wait for them to respond.
# - Changed message_DISCONNECT to properly update the tables of entities,
#   and to recognize when we are waiting to terminate, check if all entities
#   are gone, and then do the real termination.
# - Added methods _realstop and realstop, which do the actual termination
#   mentioned above. realstop can be overriden by the subclass implementer
#   to do anything he wants.
#
# Revision 1.3  1998/03/04 06:32:39  zamboni
# General debugging and cleaning up. Local starting of entities seems to
# mostly work now.
#
# Revision 1.2  1998/02/26 05:34:15  zamboni
# Added support for CONNECT messages.
# Seems to mostly work now.
#
# Revision 1.1  1998/02/26 04:55:02  zamboni
# Initial revision
#
