package com.ibm.able.examples.rules;

//====================================================================
//
// Licensed Materials - Property of IBM
//
// "Restricted Materials of IBM"
//
// Product: com.ibm.able.examples.rules
//
// (C) Copyright IBM Corp. 2003 All Rights Reserved.
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with
// IBM Corp.
//
//
//                             DISCLAIMER
//                             ----------
//
// This material contains programming source code for your consideration.
// These examples have not been thoroughly tested under all conditions.
// IBM, therefore, cannot guarantee or imply reliability, serviceability,
// performance or function of these programs.  All programs contained
// herein are provided to you "AS IS".  THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY
// DISCLAIMED.
//
//====================================================================

//====================================================================
// Imports
//====================================================================
import java.io.File;
import java.lang.reflect.Method;
import java.util.Random;

import com.ibm.able.Able;
import com.ibm.able.AbleEvent;
import com.ibm.able.AbleException;
import com.ibm.able.AbleObject;
import com.ibm.able.AbleUserDefinedFunction;
import com.ibm.able.rules.AbleRuleSet;


/**
 * This Java program (run it from the command line) creates and
 * exercises an ABLE Policy Document (ruleset) in several different
 * ways.  The DecisionName that is implemented is a
 * ProvisioningDecision.
 *
 * @version  $Revision: 1.1 $, $Date: 2003/11/25 17:26:54 $
 */
public class PolicyDemo extends AbleObject {
  //                                   YYYYMMDDVerRelModxx
  static final long serialVersionUID = 2003111400100100000L;

  //==================================================================
  //
  // Instance variables.
  //
  //==================================================================
  private Random myRandom = new Random();

  private AbleRuleSet myRuleSet = null;


  //==================================================================
  //
  // Constructors
  //
  //==================================================================
  public PolicyDemo() throws AbleException {
    this("PolicyDemo");
  }

  public PolicyDemo(String theName) throws AbleException {
    super(theName);
    init();
  }


  public void process() throws AbleException {
    runDemo();
  }


  //==================================================================
  //
  // Methods specific to this class
  //
  //==================================================================
  /**
   * Controlling logic for the demo.
   */
  public void    runDemo() {
    String lclM="runDemo";

    try {
      //--------------------------------------------------------------
      // Start ABLE's message and trace logging
      //--------------------------------------------------------------
      Able.startMessageAndTraceLogging();

      //--------------------------------------------------------------
      // Create a ruleset to hold the ProvisioningDecision policy
      // document.
      //--------------------------------------------------------------
      myRuleSet = new AbleRuleSet();
    //myRuleSet.setInferenceTraceLevel(Able.TRC_MEDIUM_INFER);

      //--------------------------------------------------------------
      // Instantiate the ruleset by reading a source rule file.
      //--------------------------------------------------------------
      String lclPath = Able.ProductDirectory+"examples"+File.separator+"rules"+File.separator;
      myRuleSet.parseFromARL(lclPath + "PolicyDemo.arl");

      //--------------------------------------------------------------
      // Create the sensors and effectors that will be called by the
      // ruleset and then hook them into the ruleset.
      //
      // It should be obvious to one skilled in the art that although
      // the referenced methods exist right here in this Java object,
      // the methods can reside in any publicly accessible Java
      // object.
      //--------------------------------------------------------------
      Class lclClass = this.getClass();

      Method                  lclMethod;
      AbleUserDefinedFunction lclUdf;

      lclMethod = lclClass.getMethod(         "realTimeNumberOfServersMethod", new Class[] {});
      lclUdf    = new AbleUserDefinedFunction("realTimeNumberOfServers",       this, lclMethod);
      myRuleSet.addUserDefinedFunction(lclUdf);

      lclMethod = lclClass.getMethod(         "realTimeNumberOfActiveConnectionsMethod", new Class[] {});
      lclUdf    = new AbleUserDefinedFunction("realTimeNumberOfActiveConnections",       this, lclMethod);
      myRuleSet.addUserDefinedFunction(lclUdf);

      lclMethod = lclClass.getMethod(         "realTimeCpuUtiliztionMethod", new Class[] {});
      lclUdf    = new AbleUserDefinedFunction("realTimeCpuUtiliztion",       this, lclMethod);
      myRuleSet.addUserDefinedFunction(lclUdf);

      lclMethod = lclClass.getMethod(         "immediatelyAddServersMethod", new Class[] {Integer.class});
      lclUdf    = new AbleUserDefinedFunction("immediatelyAddServers",       this, lclMethod);
      myRuleSet.addUserDefinedFunction(lclUdf);

      // -------------------------------------------------------------
      // Initialize the ruleset, so that it is ready to run.
      // -------------------------------------------------------------
      myRuleSet.init();

      // -------------------------------------------------------------
      // Trigger a Provisioning Decision
      // -------------------------------------------------------------
      getAndActOnProvisioningDecision(randomInt() // NumberOfServers
                                    , 10          // NumberOfActiveConnections
                                    , 12          // CPUUtilization
                                    );

      // -------------------------------------------------------------
      // Allow the policy document (ruleset) to act on its own,
      // performing periodic checks.
      // -------------------------------------------------------------
      setUpAndRunPeriodicChecks();

      //--------------------------------------------------------------
      // Trigger another Provisioning Decision
      //--------------------------------------------------------------
      getAndActOnProvisioningDecision(0   // NumberOfServers
                                     ,26  // NumberOfActiveConnections
                                     ,76  // CPUUtilization
                                     );

      // -------------------------------------------------------------
      // Route some asynchronous events to the Policy Document.  They
      // are placed on the policy document's event queue in order of
      // arrival and processed on a separate thread.
      // -------------------------------------------------------------
      routeProvisioningDecisionEventsToPolicyDoc();

    }

    // Just catch all errors here; nothing fancy.
    catch(Exception theException) {
      System.out.println("runDemo exception: " + theException);

      if (trace.isLogging()) {
        trace.text(Able.TRC_LOW,this,lclM,"!! Caught nasty exception: "
          + theException.getLocalizedMessage());
        trace.exception(Able.TRC_LOW,this,lclM,theException);
      }
    }

    // That's the demo!
    System.out.println("DRVR: done.");
  }


  /**
   * Get the input buffer from the ruleset, load it with the specified
   * data, process the ruleset, get the decision result from the
   * output buffer, and then, if necessary, call an effector to act on
   * the decision.
   */
  private void getAndActOnProvisioningDecision(int theNumberOfServers, int theNumberOfActiveConnections, int theCpuUtilization) throws AbleException {
    Integer  lclResult = new Integer(0);
    Integer  lclServers;
    Object[] lclOutBuffer;
    Object[] lclInpBuffer;

    // Obtain and load the ruleset's input buffer.
    lclInpBuffer = (Object[])myRuleSet.getInputBuffer();
    lclInpBuffer[0] = new Integer(theNumberOfServers);
    lclInpBuffer[1] = new Integer(theNumberOfActiveConnections);
    lclInpBuffer[2] = new Integer(theCpuUtilization);

    // Invoke the policy document
    myRuleSet.process();

    // Obtain the decision from the ruleset's output buffer.
    lclOutBuffer = (Object[])myRuleSet.getOutputBuffer();
    lclServers   = (Integer)lclOutBuffer[0];
    System.out.println("DRVR: Provisioning Policy indicates <" + lclServers + "> servers need to be added.");

    // If necessary, act on the decision by calling an effector.
    if (lclServers.intValue() > 0) {
      // Attempt to add the necessary number of servers.
      lclResult = immediatelyAddServersMethod(lclServers);
      // If not all servers could be added, alert someone or something.
      if (lclResult.intValue() > 0) {
        System.out.println("DRVR: Alert! <" + lclResult.intValue() + "> not added.");
      }
    }
  }


  /**
   * Allow the policy document to run unattended for a while.  It will
   * obtain data from external sensors, make decisions, and carry out
   * actions on its own, for as long as we let it.
   */
  private void setUpAndRunPeriodicChecks() {
    // Set up the policy document's timer thread and turn it loose.
    myRuleSet.setSleepTime(5000L);
    myRuleSet.setTimerEventProcessingEnabled(true);
    myRuleSet.setAbleEventProcessingEnabled(Able.ProcessingEnabled_PostingEnabled);
    myRuleSet.startEnabledEventProcessing();

    // Let it spin for a while; we'll take a nap while it does
    // periodic checks and maybe handles events routed to it.
    try {
      Thread.sleep(30000);
    }
    catch (InterruptedException e) {
      ;
    }

    // OK, enough of that.  Suspend it until we want to activate it
    // again and turn off the timer.
    myRuleSet.suspendEnabledEventProcessing();
    myRuleSet.setTimerEventProcessingEnabled(false);
  }


  /**
   * Route a few asynchronous Provision Decision events to the Policy
   * Document.  It will obtain data from each event, make decisions,
   * and carry out actions on its own, for as long as events keep
   * arriving and until we shut it off.
   */
  private void routeProvisioningDecisionEventsToPolicyDoc() throws AbleException {
    // Let the Policy Doc handle events on its own.
    myRuleSet.resumeEnabledEventProcessing();

    // Send an asynchronous event
    Object[] lclEventData = new Object[] {
      new Integer(randomInt()), // NumberOfServers
      new Integer(randomInt()), // NumberOfActiveConnections
      new Integer(randomInt())  // CPUUtilization
    };
    myRuleSet.handleAbleEvent( new AbleEvent(this,lclEventData) );

    // Send an asynchronous event
    lclEventData = new Object[] {
      new Integer(randomInt()), // NumberOfServers
      new Integer(randomInt()), // NumberOfActiveConnections
      new Integer(randomInt())  // CPUUtilization
    };
    myRuleSet.handleAbleEvent( new AbleEvent(this,lclEventData) );

    // Send an asynchronous event
    lclEventData = new Object[] {
      new Integer(randomInt()), // NumberOfServers
      new Integer(randomInt()), // NumberOfActiveConnections
      new Integer(randomInt())  // CPUUtilization
    };
    myRuleSet.handleAbleEvent( new AbleEvent(this,lclEventData) );

    // Tell the Policy Doc to quiesce its event queue.
    myRuleSet.quitEnabledEventProcessing();
  }


  // =================================================================
  //
  // Sensors and Effectors...
  //
  // ...are methods that could reside in any Java object, and, through
  // the magic of Able User-Defined-Functions (UDFs), can be attached
  // to and called by Able Rulesets (Policy Documents).  These methods
  // could also reside in a Java class that is imported directly into
  // a ruleset as a method library.
  //
  // Note: This is a demo; that means that all numbers returned by the
  // sensors and effectors are generated randomly, and do not
  // represent the state of any actual system.
  // =================================================================
  /**
   * Determines, in real time, the current number of active Servers.
   *
   * @return    The number of active servers.
   */
  public Integer realTimeNumberOfServersMethod() {
    System.out.println("** Sensor Determining current number of servers...");
    int lclInt = randomInt();
    System.out.println("**   Current number of active servers is <" + lclInt + ">.");

    return new Integer(lclInt);
  }


  /**
   * Determines, in real time, the current number of active
   * connections.
   *
   * @return    The number of active connections.
   */
  public Integer realTimeNumberOfActiveConnectionsMethod() {
    System.out.println("** Sensor determining current number of active connections...");
    int lclInt = randomInt();
    System.out.println("**   Current number of active connections is <" + lclInt + ">.");

    return new Integer(lclInt);
  }


  /**
   * Determines, in real time, the current CPU utilization.
   *
   * @return    The CPU utilization.
   */
  public Integer realTimeCpuUtiliztionMethod() {
    System.out.println("** Sensor determining current CPU utilization...");
    int lclInt = randomInt();
    System.out.println("**   Current CPU utilization is <" + lclInt + ">.");

    return new Integer(lclInt);
  }


  /**
   * Adds the specified number of servers to the current
   * configuration.
   *
   * @param     theNumber
   *            The number of servers to add.
   *            <p>
   * @return    <i>zero</i>, if all servers were added successfully;
   *            otherwise, the number is servers that could <b>not</b>
   *            be added is returned.
   */
  public Integer immediatelyAddServersMethod(Integer theNumber) {
    int theInt = theNumber.intValue();

    System.out.println("** Effector adding <" + theNumber + "> servers to current server pool.");
    int lclInt = randomInt(theInt+1);
    System.out.println("**   <" + lclInt + "> Servers added successfully.");

    if (theInt == lclInt) {                // Want == Actual?
      return new Integer(0);               // All added, no remaining.
    } else {
      return new Integer(theInt - lclInt); // Want - Actual = remaining.
    }
  }


  // Return a random integer between 0 (inclusive) and 100.
  private int randomInt() {
    return myRandom.nextInt(101);
  }

  // Return a random integer between 0 (inclusive) and the specified
  // number (exclusive).
  private int randomInt(int theInt) {
    return myRandom.nextInt(theInt);
  }


  //==================================================================
  //
  // Main method
  //
  //==================================================================
  public static void main(String[] args) {
    try {
      PolicyDemo lclDemo = new PolicyDemo();

      lclDemo.runDemo();

      System.exit(0);
    }
    catch(Throwable theThrowable) {
      if (Able.TraceLog.isLogging())
        Able.TraceLog.exception(Able.TRC_HIGH,
          "com.ibm.able.examples.rules.PolicyDemo", "main", theThrowable);
    }
  }

}
