Reference Guide > Custom Procedure Examples > Example 4: Nontransactional External Update without Compensation
 
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);
}
}
}