Tuesday, October 18, 2011

Apex Salesforce Limitation

Geregetan juga saat coding di Salesforce.com dengan Apex, ternyata cukup banyak keterbatasan yang diterapkan di sana. Memang sih, tujuannya untuk optimalisasi proses dan mencegah server berantakan gara-gara terlalu banyak proses yang berjalan secara simultan. Bosku bilang ke client "ini adalah konsekuensi menggunakan sistem multi-tenant".

Awalnya aku penasaran, apa saja sih yang dibatasi oleh Salesforce.com ini. Eh, ternyata di debug log ada penjelasannya. Berikut daftar (mungkin belum semua) batasan yang diberikan oleh Salesforce, sekedar untuk pengingat:
  • Number of SOQL queries: 100
  • Number of query rows: 50000
  • Number of SOSL queries: 20
  • Number of DML statements: 150
  • Number of DML rows: 10000
  • Number of script statements: 200000
  • Maximum heap size: 3000000
  • Number of callouts: 10
  • Number of Email Invocations: 10
  • Number of fields describes: 100
  • Number of record type describes: 100
  • Number of child relationships describes: 100
  • Number of picklist describes: 100
  • Number of future calls: 10

Gak semuanya aku paham apa maksudnya (newbie total, gan!), tapi setidaknya aku sudah dipersulit akibat adalah batasan script statements dan DML statements. Kebetulan ada client yang datanya banyak, selain itu prosesnya juga ribet.

Masih ada batasan lain sih, seperti jumlah Apex Job yang boleh dijalankan, jumlah maksimum Batch Processing (untuk import data), dan sebagainya. Belum sempat menggali semuanya. Ada yang tahu dokumentasi lengkapnya? Lagi malas baca user manual nih.

Update::
Ternyata ada cara untuk bisa mengetahui limit dari batasan-batasan tersebut, termasuk cara mengukur penggunaannya. Cekidot di sini http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_limits.htm

Wednesday, September 21, 2011

Salesforce Data Connection with PHP

Sekedar untuk mengingat aja, contoh kelas untuk koneksi PHP ke Salesforce dengan API/SOAP.





/**
  Class to handle connection to salesforce


*/
require_once("soapclient/SforcePartnerClient.php");
require_once("soapclient/BulkApiClient.php");


define('SOAP_PATH', "soapclient");
define('WSDL_XML_PARTNER', "partner.wsdl.xml");
define('WSDL_XML_ENTERPRISE', "enterprise.wsdl.xml");
define('WSDL_XML_METADATA', "metadata.wsdl.xml");


class SFConnectionClass
{
  public $connection;
  public $server = 'sand'; // option (sand or prod)
  public $username = '';
  public $password = '';
  public $token = '';
  public $sfSessionID = '';
  public $sfLocation = '';


  /*
    constructor
    // if parameter empty, use default
  */
  public function SFConnectionClass($server = 'sand', $username = '', $password = '', $token = '')
  {
    if ($server != "") $this->server = $server;
    if ($username != "") $this->username = $username;
    if ($password != "") $this->password = $password;
    if ($token != "") $this->token = $token;
    
    $this->connection = $this->getConnectionSF();
  }


  /*
    get database connection to salesforce
  */
  public function getConnectionSF()
  {
    try {


      $username = $this->username;
      $password = $this->password;
      $token    = $this->token;


      $SforceConnection = new SforcePartnerClient();
      //$wsdl_xml = ($isEnterprise) ? WSDL_XML_ENTERPRISE : WSDL_XML_PARTNER;
      $wsdl_xml = SOAP_PATH."/".$this->server."_".WSDL_XML_PARTNER;
      $SforceConnection->createConnection($wsdl_xml);
      $SforceConnection->login($username, $password.$token);
      
      $this->connection  = $SforceConnection;
      $this->sfSessionID = $SforceConnection->getSessionID();
      $this->sfLocation  = $SforceConnection->getLocation();
      
      return $SforceConnection; // soap salesforce
    }
    catch (Exception $e) {
      print_r($e);
      die();
    }
    return null;
  }
  
  /*
    function to get query result in csv format
  */
  public function fetchQueryCSV($strSQL)
  {
    $queryResult = $this->getQueryResultSF($strSQL);
    $strRes = "";
    if (isset($queryResult->records) && count($queryResult->records) > 0)
    {
      // generate header
      $arrHeader = array();
      $strHeader = "";
      foreach($queryResult->records[0]->fields AS $strKey => $objV)
      {
        $arrHeader[$strKey] = $strKey;
        $strHeader .= ($strHeader == "") ? $strKey : ",".$strKey;
      }
      if (isset($queryResult->records[0]->sobjects))
      {
        foreach($queryResult->records[0]->sobjects[0]->fields AS $strKey => $objV)
        {
          $strKey = $queryResult->records[0]->sobjects[0]->type .".".$strKey;
          $arrHeader[$strKey] = $strKey;
          $strHeader .= ($strHeader == "") ? $strKey : ",".$strKey;
        }
      }
      
      // generate detail
      foreach ($queryResult->records AS $i => $obj)
      {
        $arrTmp = array();
        foreach ($obj->fields AS $strKey => $objV)
        {
          $arrTmp[$strKey] = (string)$objV;
        }
        if (isset($obj->sobjects))
        {
          foreach($obj->sobjects AS $j => $objSO)
          { 
            foreach($objSO->fields AS $strKey => $objV)
              $arrTmp[$objSO->type.".".$strKey] = (string)$objV;
          }
        }
        $strDetail = "";
        $x = 0;
        foreach ($arrHeader AS $key)
        {
          $strVal = (isset($arrTmp[$key])) ? $arrTmp[$key] : "";
          if (strstr($strVal, ",")) $strVal = '"'.$strVal.'"';
          $strDetail .= ($x == 0) ? $strVal : ",".$strVal;
          $x++;
        }
        if ($strRes != "") $strRes .= chr(13).chr(10);
        $strRes .= $strDetail;
      }
      $strRes = $strHeader .chr(13).chr(10).$strRes;
    }
    return $strRes;
  }


  /*
    function to get query result in array of fields=>value
  */
  public function fetchQueryArray($strSQL)
  {
    $queryResult = $this->getQueryResultSF($strSQL);
    $arrRes = array();
    if (isset($queryResult->records))
    {
      foreach ($queryResult->records AS $i => $obj)
      {
        if (isset($obj->Id)) $arrRes[$i]['ID'] = $obj->Id;
        if (isset($obj->Id)) $arrRes[$i]['OBJECT_NAME'] = $obj->type;
        if (isset($obj->fields))
        {
          foreach($obj->fields AS $strKey => $objV)
            $arrRes[$i][$strKey] = (string)$objV;
        }
        if (isset($obj->sobjects))
        {
          foreach($obj->sobjects AS $j => $objSO)
          { 
            $arrRes[$i]['REFERENCES'] = array();
            foreach($objSO->fields AS $strKey => $objV)
              $arrRes[$i]['REFERENCES'][$objSO->type][$strKey] = (string)$objV;
          }
        }
      }
    }
    return $arrRes;
  }


  /*
    function to get query result - SOAP
  */
  public function getQueryResultSF($strSQL)
  {
    $response = $this->connection->query($strSQL);
    $queryResult = new QueryResult($response);


    return $queryResult;
  }
  
  /* function to execute delete data based on result of the query
  */
  public function deleteResultData($strSQL)
  {
    $arrData = $this->fetchQueryArray($strSQL);
    $strCsv = "";
    $strObjectName = "";
    foreach ($arrData AS $i => $records)
    {
      if ($records['OBJECT_NAME'] != "") $strObjectName = $records['OBJECT_NAME'];
      if ($records['ID'] != "") 
      {
        if ($strCsv != "") $strCsv .= chr(13).chr(10);
        $strCsv .= $records['ID'];
      }
    }
    
    if ($strCsv == "" || $strObjectName == "") return "ERROR: No Data!";
    
    $strCsv = "Id".chr(13).chr(10).$strCsv;
    return $this->processBulkAPI("delete", $strObjectName, $strCsv);
  }
  
  /*
    function to execute bulkAPI processing data
    input   : process type (insert/update/delete), object name, csv
    output  : result
  */
  public function processBulkAPI($type, $strObject, $strCsv = '')
  {
    if ($strCsv == "") return false;
    try {
      // BulkAPI client
      $myBulkApiConnection = new BulkApiClient($this->sfLocation, $this->sfSessionID);
      $myBulkApiConnection->setLoggingEnabled(true);
      $myBulkApiConnection->setCompressionEnabled(true);


      // Hand oops Bulk Jobs :P
      $job = new JobInfo();
      $job->setObject($strObject);
      $job->setOpertion($type);
      $job->setContentType("CSV");
      $job->setConcurrencyMode("Parallel");
      $job = $myBulkApiConnection->createJob($job);


      // Result
      $result = $myBulkApiConnection->createBatch($job, $strCsv);
      $tmp = $myBulkApiConnection->updateJobState($job->getId(), "Closed");
      $resultBatch = $myBulkApiConnection->getBatchResults($job->getId(), $result->getId());
      
      return $resultBatch;
    }
    catch (Exception $e)
    {
      return $e->getMessage();
    }
  }
  
  // function to parse csv string to array
  public function parseCsv($strCsv)
  {
    $arrResult = array();
    $arrH = array();
    $arr = explode("\n", $strCsv);
    $i = 0;
    foreach($arr AS $j => $str)
    {
      $arrD = explode(",", $str);
      if ($i == 0)
      {
        $arrH = $arrD;
      }
      else
      {
        $arrTmp = array();
        foreach($arrD AS $k=>$tmp)
        {
          $strKey = str_replace("\"", "", $arrH[$k]);
          $arrTmp[$strKey] = str_replace("\"", "", $tmp);
        }
        $arrResult[] = $arrTmp;
      }
      $i++;
    }
    return $arrResult;
  }


}
?>

Sapa tahu suatu saat berguna.
http://tinypaste.com/e4c5e

Friday, September 16, 2011

Decimal vs Integer : Sedikit Masalah Di Apex

Aku buat program dengan menggunakan Map di Apex.

Map mapData = new Map();mapData.put('01', 10000);mapData.put('02',5000000000);

Waktu di-save, gagal dengan error berikut :
Error: Compile Error: Invalid Integer: 5000000000 at line ...

Loh? Masak decimal gak bisa menampung data 5M? Gimana bisa buat program akunting di Indonesia kalau gini. Penasaran, aku tanya ke senior.

Dia mencoba menawarkan menambahkan .00 di belakang angka 5M.

mapData.put('02',5000000000.00);

Dan berhasil!!!
Aneh. Bahkan seniorkupun merasa heran kok solusi yang diberikan dia bisa berhasil.

Dugaanku, apex menganggap angka itu sebagai Integer (sesuai pesan errornya), meskipun tipe datanya sudah kuberi Decimal. Akibatnya error karena 5M melebih batas untuk integer.

Mungkin paling tepat adalah menggunakan casting data : Decimal.valueOf(5000000000). Tapi kan merepotkan :(