Example 4: Nontransactional External Update without Compensation

This custom procedure updates the contents of a file on disk where the file is nontransactional. The actual work is deferred until the commit method is called. Compensating logic is provided.

package proc;
import com.compositesw.extension.*;
import java.sql.*;
import java.io.*;
public class NonTransactional
  implements CustomProcedure, java.io.Serializable
{
  private transient ExecutionEnvironment qenv;
  private transient File dataFile;
  private transient int numRowsUpdated;
  private transient int newId;
  private transient String newFirstName;
  private transient String newLastName;
  private transient String newCompanyName;
  private transient String newPhoneNumber;
  private int oldId;
  private String oldFirstName;
  private String oldLastName;
  private String oldCompanyName;
  private String oldPhoneNumber;
  public NonTransactional() { }
  /**
   * This is called once just after constructing the class. The
   * environment contains methods used to interact with the server.
   */
  public void initialize(ExecutionEnvironment qenv)
    throws CustomProcedureException
  {
    this.qenv = qenv;
    dataFile = new File("C:/CustomProcNonTrans.txt");
    try {
      if (!dataFile.canWrite() && !dataFile.createNewFile())
        throw new CustomProcedureException("cannot write file");
    }
    catch (IOException ex) {
      throw new CustomProcedureException(ex);
    }
  }
  /**
   * Called during introspection to get the description of the input
   * and output parameters. Should not return null.
   */
  public ParameterInfo[] getParameterInfo() {
    return new ParameterInfo[] {
      new ParameterInfo("Id", Types.INTEGER, DIRECTION_IN),
      new ParameterInfo("FirstName", Types.VARCHAR, DIRECTION_IN),
      new ParameterInfo("LastName", Types.VARCHAR, DIRECTION_IN),
      new ParameterInfo("CompanyName", Types.VARCHAR, DIRECTION_IN),
      new ParameterInfo("PhoneNumber", Types.VARCHAR, DIRECTION_IN),
    };
  }
  /**
   * Called to invoke the stored procedure. Will only be called a
   * single time per instance. Can throw CustomProcedureExecption or
   * SQLException if there is an error during invoke.
   */
  public void invoke(Object[] inputValues)
    throws CustomProcedureException
  {
    //
    // Save new values for later use in 'commit'
    //
    newId = ((Integer)inputValues[0]).intValue();
    newFirstName = (String)inputValues[1];
    newLastName = (String)inputValues[2];
    newCompanyName = (String)inputValues[2];
    newPhoneNumber = (String)inputValues[3];
  }
  /**
   * Called to retrieve the number of rows that were inserted,
   * updated, or deleted during the execution of the procedure. A
   * return value of -1 indicates that the number of affected rows is
   * unknown. Can throw CustomProcedureExecption or SQLException if
   * there is an error when getting the number of affected rows.
   */
  public int getNumAffectedRows()
    throws CustomProcedureException
  {
    return numRowsUpdated;
  }
  /**
   * Called to retrieve the output values. The returned objects
   * should obey the Java to SQL typing conventions as defined in the
   * table above. Output cursors can be returned as either
   * CustomCursor or java.sql.ResultSet. Can throw
   * CustomProcedureException or SQLException if there is an error
   * when getting the output values. Should not return null.
   */
  public Object[] getOutputValues()
    throws CustomProcedureException
  {
    return new Object[] { };
  }
  /**
   * Called when the procedure reference is no longer needed. Close
   * can be called without retrieving any of the output values (such
   * as cursors) or even invoking, so this needs to do any remaining
   * cleanup. Close can be called concurrently with any other call
   * such as "invoke" or "getOutputValues". In this case, any pending
   * methods should immediately throw a CustomProcedureException.
   */
  public void close() { }
  //
  // Introspection methods
  //
  /**
   * Called during introspection to get the short name of the stored
   * procedure. This name can be overridden during configuration.
   * Should not return null.
   */
public String getName() {
    return "NonTransactional";
  }
  /**
   * Called during introspection to get the description of the stored
   * procedure. Should not return null.
   */
  public String getDescription() {
    return "This procedure performs an update to an external " +
      "nontransactional file data source.";
  }
  //
  // Transaction methods
  //
  /**
   * Returns true if the custom procedure uses transactions. If this
   * method returns false then commit and rollback will not be called.
  */
  public boolean canCommit() {
    return true;
  }
  /**
   * Commit any open transactions.
   */
  public void commit()
    throws CustomProcedureException
  {
    //
    // Save away the current values to be used for compensation
    //
    try {
      BufferedReader reader = new BufferedReader(new FileReader(dataFile));
      String line = reader.readLine();
      oldId = (line == null || line.length() == 0) ? 0 :Integer.parseInt(line);
      oldFirstName = reader.readLine();
      oldLastName = reader.readLine();
      oldCompanyName = reader.readLine();
      oldPhoneNumber = reader.readLine();
      reader.close();
    }
    catch (IOException ex) {
      throw new CustomProcedureException(ex);
    }
    //
    // Write the new data out to the file
    //
    try {
      BufferedWriter writer = new BufferedWriter(new FileWriter(dataFile));
      writer.write(Integer.toString(newId));   writer.newLine();
      writer.write(newFirstName);              writer.newLine();
      writer.write(newLastName);               writer.newLine();
      writer.write(newCompanyName);            writer.newLine();
      writer.write(newPhoneNumber);            writer.newLine();
      writer.close();
    }
    catch (IOException ex) {
      throw new CustomProcedureException(ex);
    }
  }
  /**
   * Rollback any open transactions.
   */
  public void rollback() {
    // do nothing
  }
  /**
   * Returns true if the transaction can be compensated.
   */
  public boolean canCompensate() {
    return true;
  }
  /**
   * Compensate any committed transactions (if supported).
   */
  public void compensate(ExecutionEnvironment qenv)
    throws CustomProcedureException
  {
    //
    // Restore the old data
    //
    try {
      BufferedWriter writer = new BufferedWriter(new FileWriter(dataFile));
      writer.write(Integer.toString(oldId));   writer.newLine();
      writer.write(oldFirstName);              writer.newLine();
      writer.write(oldLastName);               writer.newLine();
      writer.write(oldCompanyName);            writer.newLine();
      writer.write(oldPhoneNumber);            writer.newLine();
      writer.close();
    }
    catch (IOException ex) {
      throw new CustomProcedureException(ex);
    }
  }
}