// Copyright (c) 2001, 2003, Oracle Corporation.  All rights reserved.  
// File: NMFDemo.java

/**
 * This sample program describes how to use the ODM Java API to build and
 * apply a feature extraction model for text mining using the Non-negative
 * Matrix Factorization (NMF) algorithm.
 * 
 * This program uses the "MINING_BUILD_TEXT" dataset for building the model
 * to extract features hidden in the data.
 * The same data is used for apply to find the features in the data.
 * 
 * Proper login information (user name, password and DB URL) and data schema
 * name must be provided to run this program.
 */

// Generic Java Imports
import java.io.*;
import java.sql.Statement;
import java.util.*;
import java.sql.SQLException;

// ODM Imports
import oracle.dmt.odm.*;
import oracle.dmt.odm.data.*;
import oracle.dmt.odm.settings.function.*;
import oracle.dmt.odm.settings.algorithm.*;
import oracle.dmt.odm.task.*;
import oracle.dmt.odm.model.*;
import oracle.dmt.odm.result.*;

public class NMFDemo extends Object {

  private static DataMiningServer m_dms = null;
  private static oracle.dmt.odm.Connection m_dmsConn = null;
  private static final String DATA_SCHEMA_NAME = "schema name";
  public static void main( String args[] ) { 
    try {      
      //1. Login to the Data Mining Server
      //Create a DMS object
      m_dms = new DataMiningServer(
            "put DB URL here", //JDBC URL jdbc:oracle:thin:@<Host name>:<Port>:<SID>
            "user name", //User Name
            "password" //Password 
      );
      //Login to the DMS and create a DMS Connection
      m_dmsConn = m_dms.login();
      //Remove the objects to avoid name conflicts
      cleanup();
      //Text column needs to be indexed in order to perform text mining.
      //In this example, COMMENTS text column in MINING_BUILD_TEXT and 
      //MINING_TEST_TEXT tables are pre-indexed during the installation.  
      //However, you can comment out and modify the following code if you 
      //would like to perform context index for a different table.
      /*
       executeSql(m_dmsConn.getDBConnection(),
              "CREATE INDEX context_build_idx ON MINING_BUILD_TEXT(COMMENTS) "+
              "INDEXTYPE IS ctxsys.context PARAMETERS('nopopulate') ");
       executeSql(m_dmsConn.getDBConnection(),
              "CREATE INDEX context_test_idx ON MINING_TEST_TEXT(COMMENTS) "+
              "INDEXTYPE IS ctxsys.context PARAMETERS('nopopulate') ");
      */              
      //2. Build an NMF model
      buildModel();
      //3. Apply the model to extract features
      applyModel();
    } catch(Exception anyExp) {
      anyExp.printStackTrace(System.out);
    } finally {
      try {
        //4. Logout from the Data Mining Server
        m_dms.logout(m_dmsConn);
      } catch(Exception anyExp1) { }//Ignore
    }
  }

  /**
   * Build operation is performed by a task that requires the location of
   * the dataset, a mining function settings and the model name.
   * In this example, a FeatureExtraction model "NMFDemo_Model" is built
   * using the dataset "MINING_BUILD_TEXT" by the Non-negative Matrix
   * Factorization (NMF) algorithm.
   * This model will be used to find the features present in the data.
   */
  public static void buildModel()
    throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification for build
    PhysicalDataSpecification pds = createPDS("MINING_BUILD_TEXT", "CUST_ID");
    //2. Create a NMF algorithm settings
    NMFAlgorithmSettings nmfAlgo = new NMFAlgorithmSettings(10, 0.005f);
    //3. Create & Store Mining Function Settings
    FeatureExtractionFunctionSettings fxmfs =
          FeatureExtractionFunctionSettings.create(
                  m_dmsConn,      //DMS Connection
                  nmfAlgo,        //NMF algorithm settings
                  pds,            //Build data specification
                  DataPreparationStatus.unprepared, //Default preparation status
                  new Integer(7)  //Maximum number of features
          );
    //4. Specify the text attribute
    fxmfs.adjustAttributesType(
          new String[] {"COMMENTS"}, AttributeType.text);
    //5. Prevent private information (customer identifier and name) from being
    //   used in model building
    fxmfs.adjustAttributeUsage(
          new String[] { "CUST_ID" }, AttributeUsage.inactive);
    //6. Store the function settings
    fxmfs.store(m_dmsConn, "NMFDemo_Model_MFS");
    //5. Create a task to build an NMF model
    MiningBuildTask nmfBuildTask = new MiningBuildTask(
                  pds,                  //Build data specification
                  "NMFDemo_Model_MFS",  //Mining function settings name
                  "NMFDemo_Model"       //Mining model name
                  );
    //5. Store and execute the task
    executeTask(nmfBuildTask, "NMFDemo_Model_BuildTask");
  }

  /**
   * Apply operation is performed by a task that requires the location of
   * the dataset, the model name, the output specification and the location
   * of the output.
   * 
   * The result is persisted with a name ("NMFDemo_ApplyResults"), by which
   * specifics of the apply operation can be accessed later.
   * The output table "NMFDemo_ApplyOutput" is specified by a MiningApplyOutput
   * object to contain the feature identifier and the match quality for feature
   * extraction models.
   * 
   * In this example, the NMF model is applied with the "MINING_BUILD_TEXT"
   * dataset to obtain the feature identifier and the match quality.
   */
  public static void applyModel()
    throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification for build
    PhysicalDataSpecification pds = createPDS("MINING_BUILD_TEXT", "CUST_ID");
    //2. Create MiningApplyOutput & apply output table/view details
    //2.a Create an empty MiningApplyOutput object
    MiningApplyOutput mao = new MiningApplyOutput();
    //2.b The top 3 predicted features with their match qualities will be
    // contained in the apply output.
    // The output table will include FEATURE_ID, MATCH_QUALITY columns
    // together with the default CASE_ID column that is automaticall copied
    // from the input table.
    ApplyMultipleScoringItem amsi = new ApplyMultipleScoringItem(
        new Attribute("FEATURE_ID", DataType.intType),    // column for feature
        new Attribute("MATCH_QUALITY", DataType.floatType), // for match quality
        3);     // top 3 features wanted in the apply output
    //2.c Add the specification to MiningApplyOutput
    mao.addItem(amsi);
    //2.d Validate the specification before use
    mao.validate();
    //3. Output table location details
    LocationAccessData outputTable =
            new LocationAccessData("NMFDemo_ApplyOutput", DATA_SCHEMA_NAME);      
    //4. Create a task to apply the NMF model
    MiningApplyTask applyTask = new MiningApplyTask(
                  pds,              //test data specification
                  "NMFDemo_Model",  //Input model name
                  mao,              //MiningApplyOutput object
                  outputTable,      //Apply output table location details
                  "NMFDemo_ApplyResults" //Apply results name
                  );
    //5. Store and execute the task
    if( executeTask(applyTask, "NMFDemo_Model_ApplyTask") ) {
      //6. Restore & display apply results
      MiningApplyResult applyResult = 
              MiningApplyResult.restore(m_dmsConn, "NMFDemo_ApplyResults");
      System.out.println(applyResult.toString());
    }
  }

  /**
   * Executes the specified task after storing in with the specified name.
   */
  private static boolean executeTask(MiningDataTask task, String taskName)
    throws ODMException, SQLException
  {
    boolean isTaskSuccess = false;
    task.store(m_dmsConn, taskName);
    task.execute(m_dmsConn);  // submit the task for asynchronous execution
    System.out.print(taskName + " is started, please wait. ");
    //Wait for completion of the task
    MiningTaskStatus taskStatus = task.waitForCompletion(m_dmsConn);
    //Check the status of the task after completion
    isTaskSuccess = taskStatus.getTaskState().equals(MiningTaskState.success);
    if( isTaskSuccess ) { //Task completed successfully
      String action = (task instanceof MiningApplyTask)? "applied": "built";
      System.out.println("NMFDemo_Model " + action + " successfully");
    } else {  //Task failed
      String action = (task instanceof MiningApplyTask)? "apply": "build";
      throw new ODMException("NMFDemo_Model " + action + " failed." +
                "\nFailure description: " + taskStatus.getStateDescription() );
    }
    return isTaskSuccess;
  }

  /**
   * Cleanup the previously created objects if any. 
   */
  private static void cleanup() throws ODMException, SQLException
  {
    //Remove the function settings
    try {
      FeatureExtractionFunctionSettings.remove(m_dmsConn, "NMFDemo_Model_MFS");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the tasks
    try {
      MiningBuildTask.remove(m_dmsConn, "NMFDemo_Model_BuildTask");
    }catch(Exception anyExp1) { }//Ignore
    try {
      MiningApplyTask.remove(m_dmsConn, "NMFDemo_Model_ApplyTask");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the model
    try {
      MiningModel.remove(m_dmsConn, "NMFDemo_Model");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the apply results together with apply output table
    try {
      MiningApplyResult.remove(m_dmsConn, "NMFDemo_ApplyResults", true);
    }catch(Exception anyExp1) { }//Ignore
  }

  /**
   * Create a physical data specification with the specified data table name.
   * The sequence ID column name must be specified for both build and apply
   * of NMF.
   */
  private static PhysicalDataSpecification createPDS(
                          String tableName, String sequenceIdColName)
    throws ODMException
  {
    LocationAccessData lad = new LocationAccessData( 
                  tableName,    //Data Table Name
                  DATA_SCHEMA_NAME //Schema Name
                  );
    NonTransactionalDataSpecification ntds =
                  new NonTransactionalDataSpecification(lad);
    Attribute sequenceId = new Attribute(sequenceIdColName, DataType.intType);  
    ntds.setSequenceAttribute(sequenceId);
    return ntds;
  }

  /**
   * Execute sql statement in a given database connection. 
   */
  public static void executeSql ( java.sql.Connection conn, String sql) throws SQLException
  {
    Statement stmt = conn.createStatement ();
    try {
      boolean okay = stmt.execute(sql);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    finally {
      stmt.close();
    }
  }
}