Java Code Samples

From AgileApps Support Wiki
Revision as of 00:16, 1 October 2014 by imported>Aeric (→‎Send Notification Messages to Followers)

1 About the Code Samples

These samples assume basic familiarity with the use of the platform's Java APIs. In many cases, it is also helpful to be conversant with the platform's System Objects.
Learn more:

2 Class Template

Use this class as a template for a class that accesses record data and uses it to perform some operation.

<syntaxhighlight lang="java" enclose="div">

package com.platform.yourCompany.yourApplication;

// Basic imports import com.platform.api.*; import java.util.*;

// Reference static functions without having to specify the Functions class. // So Functions.throwError() can be written as throwError(). // (Code is shorter that way, but it's less obvious where things are defined.) import static com.platform.api.Functions.*;

// These are needed for advanced operations. //import com.platform.beans.*; //import static com.platform.api.CONSTANTS.*;

public class YourClass {

  public void log(String msg) throws Exception {
    // Put the message in the log.
    Logger.info(msg, "Your Class");
  }
       
  public void debug(String msg) throws Exception {
    // Display message in a popup or at top of page, depending on context, and log it.
    Functions.showMessage(msg);
    log(msg);
  }
  public void doSomething(Parameters p) throws Exception
  {
     try {
        debug("Started");
        String objectID = p.get("object_id");
        String recordID = p.get("id");  
        // Define the parameters for some operation
        Parameters params = Functions.getParametersInstance();
        params.add("key", "value");
        //...
        // Do it.
        // Result.getCode() >= 0 on success, -1 on failure
        Result r = Functions.doSomething(params);
        if (r.getCode() < 0) {
           // Display message to user, add an entry to debug log, and
           // roll back the current transaction (no changes are committed).
           String msg = "Something failed";
           log(msg);
           Functions.throwError(msg + "\n" + r.getMessage() );      
        }  
        debug("Success");
     } catch (Exception e) {
        // Catch surprises, display a popup, and put them in the log.
        String msg = "Unexpected exception";
        log(msg + ":\n" + e.getMessage() );
        Functions.throwError(msg + " - see debug log");      
     }
  }

} // end class </syntaxhighlight>

Thumbsup.gif

Best Practice:

  1. Wrap code in a try..catch block, to guard against unexpected exceptions. (If not caught, they are simply ignored, and the method fails silently.)
  2. When you detect an error, put a detailed message into the Debug Log. Then call Functions.throwError() to generate an exception, display a message for the user, and roll back the current transaction.

Additional Notes:

  • Object names and IDs
  • Most APIs take either object name or object ID. (Only a few require object ID.)
  • For most operations you'll know which object you're operating on, so you'll specify the object name in a string.
  • But the object ID is also available in the incoming Parameters, when you need it.
  • List of Parameters
  • Add this line to the code above to put a complete list of incoming parameters into the debug log:
<syntaxhighlight lang="java" enclose="div">

// Generate a newline-separated list of parameters debug( p.toString().replace(",", ",\n") ); </syntaxhighlight>

3 Add a Task to a Record

This example uses the addRecord API to create a new Task associated for the current record.

<syntaxhighlight lang="java" enclose="div">

import com.platform.api.Functions; import com.platform.api.Parameters; import java.util.*;

public class RecordAdditions {

  public void addTask(Parameters p) throws Exception
  {      
      String objectID = p.get("object_id");
      String recordID = p.get("id"); 
      String subject = "New Task Record";
      String description = "This task needs to be accomplished.";
      String relatedTo = objectID + ":" + recordID; //ex: "cases:99999999"
      // Create the Task parameters
      Parameters taskParams = Functions.getParametersInstance();
      taskParams.add("subject", subject);
      taskParams.add("description", description);
      taskParams.add("related_to", relatedTo);
      taskParams.add("due_date", new Date() );
         // Add other fields like owner_id, as required     
     
      // Add the Task
      Result r = Functions.addRecord("tasks", taskParams);
      // Check the result here. 
      // On success, Result.getCode() returns zero (0)
      // On failure, it returns -1
  }

} </syntaxhighlight>

Notepad.png

Note: Since a Task can be attached to a record in any object, related_to is a Multi Object Lookup field. It's data value therefore contains both an object identifier and the record identifier, separated by a colon.
Learn more: Field Type Reference

4 Batch Update of Related Record Owners

This example assumes that some collection of records is divided into "batches", and that each batch has a single owner. A Batch object contains header records, and each record in the collection has a lookup to the Batch object - which makes them related records.

The goal is to update all of the Related records when there is a change to the Batch record. In this case, the idea is to change the owner of all Related records whenever the Batch record is assigned to a new owner.

The solution is to create a Java method that does the updates, and invoke it only when the owner field changes in the Batch record.

4.1 Defining the Class

Here's a template for the class. It assumes that:

  • The name of the batch object is "Batch_Controls"
  • The name of the object with the related records is "Related_Details"
  • The name of the Lookup field in that object is lookup_field

(Note that object and field names are somewhat different from the display labels that appear on screen. In particular, note that spaces are replaced by underscores.)

<syntaxhighlight lang="java" enclose="div">

package com.platform.yourCompany.yourPackage;

import com.platform.api.*; import java.util.*;

public class BatchProcessing {

 /**
  * Process records related to the current record.
  */
 public void updateRelatedRecords(Parameters p) throws Exception
 {
   try { 
      // Object names. (The internal names, not the display labels.)
      String batchObject = "Batch_Controls";  // Not used. Just for clarity.
      String relatedObject = "Related_Details";
      // Get the owner of the current batch record
      //String objectID = p.get("object_id"); // Expected to be the Batch_Controls object
      String batchRecord = p.get("id");
      String batchName = p.get("some_value_that_identifies_the_batch");
      String batchOwner = p.get("owner_id");
      // Search for Related records
      Result searchResult =
         Functions.searchRecords(relatedObject , "id",  // Return record ID from search
            "lookup_field equals '" +batchRecord+ "'"); // lookup_field equals '9999999'
      int searchCode = searchResult.getCode();
      if (searchCode < 0) {
         String msg = "Error searching for Related records";
         log(msg + ":\n" + searchResult.getMessage());
         Functions.throwError(msg);                    
      } else if (searchCode == 0) {
         log("No related records found for "+batchName);
      } else {
         // Records were found (searchCode equals the number)
         ParametersIterator pit = searchResult.getIterator();
         while(pit.hasNext())
         {
            // Get the record ID, for use when updating
            Parameters relatedParams = pit.next();            
            String relatedRecordID = relatedParams.get("id");
            // Specify the field to update
            Parameters params = Functions.getParametersInstance();
            params.add("owner_id", batchOwner);
            // Update related record owner. (Functions.updateRecord() could 
            // also be used, but this method has an option to send an email.)
            Result updateResult = Functions.changeOwnerShipInfo(
               relatedObject, relatedRecordID, batchOwner, false); 
               //Set last param to true to send new owner a notification msg.
            int resultCode = updateResult.getCode();
            if (resultCode < 0) {
               // Some error happened.
               String msg = "Update failed";
               log(msg + ":\n" + updateResult.getMessage()); // Log the details
               Functions.throwError(msg + ".");        // Abort and display error
            }
         } //while
      }  //else
   } catch (Exception e) {
      // Catch surprises and report them.
      String msg = "Unexpected exception";
      log(msg + ":\n" + e.getMessage() );
      Functions.throwError(msg);      
   }
 } // method
 
 public void log(String msg) throws Exception {
   // Put the message in the log.
   Logger.info(msg, "BatchProcessing");
 }
      
 public void debug(String msg) throws Exception { 
   // For interactive debugging. Displays in a popup
   // or at top of user's page, depending on context.
   Functions.showMessage(msg);
   log(msg);
 }

} // end class </syntaxhighlight>

4.2 Creating the Update Rule

The next step is create an event Rule that runs only when the owner_id field changes in a Batch record:

  1. GearIcon.png > Objects > Batch > Business Rules
  2. Event Rules, On Batch (record): Owner Changed
  3. [New Rule]
  4. Action: Invoke the Java method, updateRelatedRecords()

4.3 Learn more

5 Manage Files

Files can be added to a specific field in a record, or added to the list of attachments.

5.1 Add a File to a Record

If a record contains a field of type File, use this code to add file content to that field.

For example, here we are adding an image for a book's cover. We assume that image was retrieved as a byte-array using a Web Service or an HttpConnection. The byte array is then passed to the method below, which converts it to an encoded string and stores it in the platform.

<syntaxhighlight lang="java" enclose="div">

import com.platform.api.*; import com.platform.beans.*; import com.platform.api.utility.*; // Encoding class

public class RecordAdditions {

  /**
   * Add a byte[] of document/image content to a record in the Books object
   */
  public void addBookCover(byte[] bytes) throws Exception
  {
     Base64Codec base64 = new Base64Codec();
     String encodedString = base64.encodeToString(bytes); 
     PlatformFileBean file = new PlatformFileBean("cover image", encodedString); 
     // Add the file to a new record in the Books object, in the "bookCover" field
     Parameters params = Functions.getParametersInstance();
     params.add("bookCover", file);
     Result result = Functions.addRecord("Books", params); 
     // Alternatively, update an existing record in the Books object
     // (Here the record ID is hardcoded. Generally, it would be passed as a parameter.)
     //String recordID = "76799333"; 
     //Result result = Functions.updateRecord("Books", recordID, params);
  }

} </syntaxhighlight>

5.2 Add and Access Record Attachments

5.2.1 Generate an Attachment

This example uses a Document Template to generate a PDF or HTML document, and then attaches the document to the current case. It is expected that the method will be invoked from a Rule.

In outline, the process is:

  1. Get the record ID from the incoming method parameters.
  2. Use the generateDocument API to create a PDF (or HTML) document from an existing template.
  3. Use the getDocument API to retrieve it, in the form of a PlatformFileBean.
  4. Use the addRecord API to attach the document to the case.
<syntaxhighlight lang="java" enclose="div">

package com.platform.yourCompany.yourPackage;

import com.platform.api.*; import com.platform.beans.*; //import java.util.*;

public class UtilityFunctions {

 // This signature allows the method to be invoked from a rule.
 // We assume it is invoked on a Case record.
 public void generateAttachment(com.platform.api.Parameters inParams)
    throws Exception
 {
    String documentTitle = "PUT TITLE OF GENERATED DOCUMENT HERE";
    String templateID = "PUT ID OF DOCUMENT TEMPLATE HERE";
    // Get the record ID from the incoming parameters
    String objectID = inParams.get("object_id");
    String recordID = inParams.get("id");
    // Generate the document
    Result result = Functions.generateDocument(objectID, recordID, templateID, 
                                               CONSTANTS.DOCUMENT.HTML);
                                         // or CONSTANTS.DOCUMENT.PDF 
    int resultCode = result.getCode();
    if (resultCode < 0) {
       String msg = "Document generation failed";
       Logger.info(msg + ":\n" + result.getMessage(), "genAttachment");
       Functions.throwError(msg + ".");
       return;
    }
    // Retrieve the document as a PlatformFileBean
    String docID = result.getId();
    result = Functions.getDocument(docID);
    resultCode = result.getCode();
    if (resultCode < 0) {
       String msg = "Failed to retrieve the document";
       Logger.info(msg + ":\n" + result.getMessage(), "genAttachment");
       Functions.throwError(msg + ".");
       return;
    }
    Parameters docParams = result.getParameters();
    PlatformFileBean fileBean = docParams.getPlatformFileBean(docID);
    // Add the document as an attachment
    Parameters params = Functions.getParametersInstance();
    params.add("title", documentTitle);
    params.add("file_field", fileBean );
    params.add("related_to", objectID+":"+recordID);
    result = Functions.addRecord("attachments", params);
    resultCode = result.getCode();
    if (resultCode < 0) {
       String msg = "Failed to attach document to case";
       Logger.info(msg + ":\n" + result.getMessage(), "genAttachment");
       Functions.throwError(msg + ".");
       return;
    }
 }

} </syntaxhighlight>

Thumbsup.gif

Tip: {{{1}}}

5.2.2 Add an External Attachment

Use this code to add an image, document, or file to a record as an attachment. As before, the byte array is presumed to have come from a Web Service or HttpConnection:

<syntaxhighlight lang="java" enclose="div">

package com.platform.yourCompany.yourApplication;

import com.platform.api.*; import com.platform.beans.*; import com.platform.api.utility.*; // Encoding class

public class RecordAdditions {

  /**
   * Add a byte[] of document/image content to the current record as an attachment.
   */
  public void addAttachment(Parameters p, String fileName, byte[] bytes) throws Exception
  {
     // Get the ID of the current record
     String objectID = p.get("object_id");
     String recordID = p.get("id"); 
     String relatedTo = objectID + ":" + recordID; //ex: "cases:99999999"
     Base64Codec base64 = new Base64Codec();
     String encodedString = base64.encodeToString(bytes); 
     PlatformFileBean file = new PlatformFileBean(fileName, encodedString); 
     // Add the file as an attachment to the current record
     Parameters params = Functions.getParametersInstance();
     params.add("title", fileName);
     params.add("file_field", file);
     params.add("related_to", relatedTo);
     Result result = Functions.addRecord("attachments", params); 
  }
  /**
   * Test the addAttachment() method. Invoke this method from a Rule 
   * and inspect the results in the GUI. (This method can be executed
   * from a Rule, because it has the required signature.)
   */
  public void addAttachmentTest(Parameters p) throws Exception
  {
     String fileName = "testfile.txt";
     String fileContent = "This is a test file.";
     byte[] bytes = fileContent.getBytes();
     addAttachment(p, fileName, bytes);
  }

} </syntaxhighlight>

5.2.3 Process Attachments

Use this method to process attachments for the current record:

<syntaxhighlight lang="java" enclose="div">
  /**
   * Process the current record's attachments. To test this method, invoke
   * it from an update Rule and inspect the Debug Log.
   */
  public void processAttachments(Parameters p) throws Exception
  {
     // Get the ID of the current record
     String objectID = p.get("object_id");
     String recordID = p.get("id");
     String relatedTo = objectID + ":" + recordID;     //ex: "cases:99999999"
     Result searchResult = 
         Functions.searchRecords("attachments", "*",   //all fields
              "related_to equals '" +relatedTo+ "'");  //related_to equals 'cases:9999999'
     int searchCode = searchResult.getCode();
     if (searchCode < 0)
     {
        String msg = "Error searching Attachments";
        Logger.info(msg + ":\n" + searchResult.getMessage(), "Search");
        Functions.throwError(msg);                     
     }
     else if (searchCode == 0)
     {
        Logger.info("No attachments found for "+relatedTo, "Search");
     }
     else
     {
        // Records were found (searchCode equals the number)
        Logger.info(searchCode+ " attachments found for " + relatedTo, "Search");
        ParametersIterator pit = searchResult.getIterator();
        while(pit.hasNext())
        {
           // Log the search result. Optionally test for a specific record.
           Parameters attachParams = pit.next();             
           String title = attachParams.get("title");
           Logger.info("Attachment title: "+title, "Search");
           if (title.equals("...test for a specific attachment...")) {
              // Operate on a specific attachment here
              break;
           }
           // Work with the search result. 
           // Here, we assume it is text and put it into the log/
           PlatformFileBean file = attachParams.getPlatformFileBean("file_field");
           String encodedContent = file.getEncodedFileContent();
           byte[] bytes = base64.decode(encodedContent); 
           String text = new String(bytes);
           Logger.info("Attachment data: " + text, "Search");
           // ...
        }
     }
  }

</syntaxhighlight>

6 Set Case Status According to Related Tasks

This code sample sets the Case state to Resolved or Pending, depending on the status of its related Tasks. Comments in the code explain its behavior. The expectation is that code is invoked by a Rule that is triggered when a Task's status changes.

<syntaxhighlight lang="java" enclose="div">

package com.platform.acme.demo.TaskProcessing;

import com.platform.api.*; import static com.platform.api.Functions.*;

public class TaskProcessing {

 public void log(String msg) throws Exception {
   // Put the message in the log.
   Logger.info(msg, "TaskProcessing");
 }
      
 public void debug(String msg) throws Exception {
   // Display message in a popup or at top of page, depending on context, and log it.
   Functions.showMessage(msg);
   log(msg);
 }
 /*
  * Call this code whenever a task changes status.
  *   - In the Tasks object, create an Event Rule that runs when a record is updated
  *   - Run the Rule when this expression is true: ISCHANGED(status)
  * Mark the case the task is attached to as "Resolved" if all tasks are completed.
  * Mark it "Pending" if all uncompleted tasks are pending (i.e. waiting on someone else).
  * NOTES: 
  *  1. This code assumes that "Waiting" has been added to the list of "Case Task Status" values
  *  2. Only processes that have started generate tasks, so all relevant processes should
  *     launched manually or by rule before any tasks are completed. Otherwise, the case
  *     status may be prematurely set to "Resolved".
  */
 public void setCaseStatus(Parameters input_params) throws Exception
 {
   try {
     // Global Picklist "Case Status" defines the values for cases.
     String caseNew = "1";
     String caseOpen = "2";
     String casePending = "3";
     String caseResolved = "4";
     String caseClosed = "5";
     String caseReopened = "6";
     
     // Global Picklist "Case Task Status" defines the values for tasks.
     String taskOpen = "1";
     String taskCompleted = "2"; 
     String taskApproved = "3";
     String taskRejected = "4";
     String taskWaiting = "5";
     // Default case status depends on the current task status.
     String taskID = input_params.get("id");
     String taskStatus = input_params.get("status");
     String caseFlag = "";
     if (taskStatus == taskCompleted) { 
        caseFlag = caseResolved;
     } else if (taskStatus == taskWaiting) {
        caseFlag = casePending;
     } else {
        // TODO: If case status is currently Resolved or Closed, it should change to Reopened.
        //       (We ignore that scenario here. Fix it in production.)
        return;
     }
          
     // Format of the related_to field is object_id:record_id
     String relatedCase = input_params.get("related_to");
     String caseObject = relatedCase.substring(0, relatedCase.indexOf(":"));
     String caseID = relatedCase.substring(relatedCase.indexOf(":")+1, relatedCase.length());
        //debug("Case Obj: "+ caseObject +", ID: "+ caseID +",  Task: "+ taskID);
     // Get all task records related to the same case. Return the status field for each record.
     Result result = Functions.searchRecords("tasks","status,id","related_to = '"+relatedCase+"'");
     int resultCode = result.getCode();
     if (result.getCode() < 0) {
        // Display message to user and add an entry to debug log.
        String msg = "Task search failed. Code="+result.getCode();
        log(msg);
        Functions.throwError(msg + "\n" + result.getMessage() );      
     }  
     
     // Search returned a list of tasks. Examine each of them.
     ParametersIterator iterator = result.getIterator();
     while (iterator.hasNext())
     {
        Parameters params = iterator.next();
        if (taskID == params.get("id")) continue;
           // The input record is included in the search results. Ignore it. 
        String status = params.get("status");
        if ( status.equals(taskOpen) ) 
        {
           // If there is an open task, case status does not change.
           caseFlag = "";
           break;
        }
        else if ( status.equals(taskWaiting) )
        {
           // If all remaining tasks are completed or waiting, 
           // this setting will stand.
           caseFlag == casePending;               
        }
        else
        {
           // Ignore completed/approved/rejected tasks.           
           continue;
        }
     }
     
     if (caseFlag != "")
     {
        // All tasks are either waiting or completed. 
        // Set the case status accordingly
        Parameters fcn_params = Functions.getParametersInstance();
        fcn_params.add("status", caseFlag);
        Functions.updateRecord(caseObject, caseID, fcn_params);
     }
     
   } catch (Exception e) {
      // Catch surprises, display a popup, and put them in the log.
      String msg = "Unexpected exception";
      log(msg + ":\n" + e.getMessage() );
      Functions.throwError(msg + " - see debug log");      
   }
 }

} </syntaxhighlight>

7 Add and Access Notes

When you add a Task or Attachment, an entry is added to the record history. But when you add a Private Note, you add it directly to the History object. There, the category field (value ="29") distinguishes Private Notes from other entries in the history, including email messages and other activities.

To add a Private Note to the history, therefore, you must be sure to set that field value. Similarly, it must be part of the search criteria when retrieving history records, to eliminate all but the Private Note records in that object.

Learn more: Adding and Accessing Related System Object Records

7.1 Add a Note

This example adds a note with special handling instructions to a claim record. In this scenario, there is a Rule that is triggered by adding a record when certain conditions are met (for example, when the record pertains to a particular customer, or when a particular flag has been set). The Rule then invokes the method defined here.

To add the note, a record is added to the History object. The record is identified as a Private Note and set up so it references the appropriate Claim.

<syntaxhighlight lang="java" enclose="div">

import com.platform.api.*; import java.util.*;

public class RecordAdditions {

  public void addNote(Parameters p) throws Exception
  {      
     // Get the ID of the current record
     String objectID = p.get("object_id");
     String recordID = p.get("id");
     // Formulate the Lookup-target for the note.
     // (A note can be added to a record in any object. So the Lookup target
     //  is a combination of an object identifier and a record identifier.)
     String relatedTo = objectID + ":" + recordID; //ex: "cases:99999999"
     // HTML tags can be included in the text
     String noteText = "Special handling instructions:
...notes...";
     // Create the Note parameters
     Parameters noteParams = Functions.getParametersInstance();
     noteParams.add("description", noteText);
     noteParams.add("related_to", relatedTo);
     noteParams.add("category", "29");
     
     // Add the Note
     // result code is zero on success, -1 on failure
     Result r = Functions.addRecord("history", noteParams);
     if (r.getCode() < 0) {
        // Display message to user, add an entry to debug log, and
        // roll back the current transaction (no changes are committed).
        Functions.throwError("Failed:\n  " + r.getMessage() );      
     }        
  } 

} </syntaxhighlight>

Notes:

  • The string "29" specifies a Private Note. The values are defined in a global picklist.
    To see the full list, go to GearIcon.png > Customization > Global Picklists > History Category.
  • HTML tags included in the note text are interpreted when displayed. So this:
<b>Special handling instructions:</b><br>...notes...
displays like this:
Special handling instructions:
...notes....

7.2 Access Notes

For Tasks and Attachments, you search the appropriate object looking for records that have a related_to value equal to the ID of the record you're interested in. To access notes, you search the History object, and you add one additional filter to restrict to the search to records who have the value "29" in the record's category field. That's the value that identifies the record as a Private Note, rather than an email or the recording of some action that was taken on the record.

<syntaxhighlight lang="java" enclose="div">
  /**
   * Process the notes attached to the current record. To test this
   * method, invoke it from an update Rule and inspect the Debug Log.
   */
  public void processNotes(Parameters p) throws Exception
  {
     // Get the ID of the current record
     String objectID = p.get("object_id");
     String recordID = p.get("id");
     String relatedTo = objectID + ":" + recordID;     //ex: "cases:99999999"
     Result searchResult = 
         Functions.searchRecords("history", "*",   //all fields
             "related_to equals '" +relatedTo+ "' AND category='29'");  
            //related_to equals 'cases:9999999' AND category = '29'
     int searchCode = searchResult.getCode();
     if (searchCode < 0)
     {
        String msg = "Error searching History";
        Logger.info(msg + ":\n" + searchResult.getMessage(), "Search");
        Functions.throwError(msg);                     
     }
     else if (searchCode == 0)
     {
        Logger.info("No notes found for "+relatedTo, "Search");
     }
     else
     {
        // Records were found (searchCode equals the number)
        Logger.info(searchCode+ " notes found for " + relatedTo, "Search");
        ParametersIterator pit = searchResult.getIterator();
        while(pit.hasNext())
        {
           // Log the results. Do other processing as needed
           Parameters noteParams = pit.next();
           String text = noteParams.get("description");
           Logger.info("Note: " + text, "Search");
           if (text.contains("...test for a specific note...")) {
              // Operate on a specific attachment here
              break;
           }
        }
     }
  }

</syntaxhighlight>

8 Send an Email Message

Start by getting an email address. Here, we'll get it from the record for a Contact with a known ID ("John Smith"):

<syntaxhighlight lang="java" enclose="div">

Result getContactRecordResult = Functions.getRecord("CONTACT",

   "first_name,last_name,email", contactRecID);

Logger.info("Sending Email to contact of Hello World Account..with email address:"

               +(getContactRecordResult.getParameters()).get("email"), "SendMail");

</syntaxhighlight>

Then use the sendEmailUsingTemplate API to send a message to that person. In order, the parameters are:

  • Related object identifier
  • Identifier of the related record which is contactRecID that was retrieved previously
  • To list which is set by making a nested call to Parameters.get to get the email address of the contact that was just retrieved
  • Cc list which is set by making a nested call to Parameters.get to get the email address of the contact that was just retrieved
  • Description (a text string)
  • Identifier of an Email Template. The template is evaluated at run time, and values are substituted for template variables. The result becomes the body of the message.
  • List of Document Templates identifiers to send as attachments (not used in this example).
Note:
Do not choose a template based on a JSP page for use as an attachment.
Learn more: JSP Attachment Deprecation
  • List of document identifiers in your documents folder to send as attachments (not used in this example)
<syntaxhighlight lang="java" enclose="div">

Result sendEmailResult = Functions.sendEmailUsingTemplate("CONTACT", contactRecID,

         (getContactRecordResult.getParameters()).get("email"),
         (getContactRecordResult.getParameters()).get("email"),
         "Sending Email to Hello World's Primary Contact - John Smith",
         "1869974057twn1678149854", "", ""); 

Logger.info("Done sending mail from Hello World account's Contact John Smith",

           "SendMail");

if(sendEmailResult.getCode() != 0) {

   Logger.info("Error in sending email!" + sendEmailResult.getMessage(), 
               "SendMail"); 

} else {

   Logger.info("Success on sendEmail, check inbox : " + sendEmailResult.getMessage(), 
               "SendMail");

} </syntaxhighlight>

Like other Java API calls such as addRecord and searchRecords, sendEmailUsingTemplate returns a Result object whose methods you can call to check the result of the call. When sendEmailUsingTemplate succeeds, Result.getCode returns zero (0).

9 Send Notification Messages to Followers

This code sample sends an email to everyone who registered themselves as a "follower" of a case, or some other record.

...

Learn more:

9.1 Setup and Testing

  1. Use the Object Construction Wizard to create the Followers object and a one-to-many relationship between Cases (for example) and Followers. (The wizard creates a Lookup field called related_to.)
  2. Use the Case form (which as a new Related Information section) to add yourself as a follower to a record.
  3. Create an Email Template to use when sending notifications. Record the Template ID.
  4. Create a class using the code below. Fill in the Template ID.
  5. Create an update rule that invokes the notifyFollowers() method defined in the code.
  6. Update the

9.2 Code

<syntaxhighlight lang="java" enclose="div">

</syntaxhighlight>

10 Invoke Web Services Programmatically

These examples show:

  • How to invoke a Web Service from Java Code
  • How to call that code from a JSP page
  • How to add a button to a form, and invoke that code (or any other) when the button is clicked.

10.1 Invoke a Web Service from Java Code

This code sample invokes a Web Service directly from a Java class. The Web Service used in this example is one of the platform's REST APIs. In this case, it's the API to get a record. That API is called as though it is an external service, using the same techniques you will employ for any services you connect to.

In this case:

  • The method is invoked by a process, macro, or Rule that is attached to some record.
  • We assume that the record contains an orderNum field that identifies an existing order.
  • The Web Service is used to retrieve the order date and invoice number for that order.
  • In the examples that follow, the orderNum is entered into a record form.
  • If the orderNum is "1" or "2", we return test values, turning the class into a "mock stub" for the proxy. For "3", we assume that an error occurred, and return no data.
  • We assume that the service address is http://orders.com/order, and that the number of the order to retrieve is passed as part of the URL: http://orders.com/order/{orderNum}. That strategy keeps the sample code simple.
  • The structure of the returned data looks like this:
<syntaxhighlight lang="xml" enclose="div">

<ORDER>

   <ORDERNUM>...</ORDERNUM>
   <ORDERDATE>...</ORDERDATE>
   <INVOICENUMBER>...</INVOICENUMBER>

</ORDER> </syntaxhighlight>

  • We assume that the Java orders package contains the order-processing classes.
  • In that package, we create a class called WebServiceProxy, to manage connections to the external service.
  • That class defines the getOrderDetails method, which uses the platform's HttpConnection Class to connect to the external service.
  • The returned data structure is parsed using Java's XPathFactory.
Invoking the Service

The method defined in this class invokes the web service:

<syntaxhighlight lang="java" enclose="div">

package com.platform.yourCompany.orders;

import com.platform.api.*; import java.util.*; import java.io.StringReader; import javax.xml.xpath.*; import org.xml.sax.*;

import static com.platform.api.Functions.*;

public class WebServiceProxy {

 public static final String WS_URL = "http://orders.com/order/";  
 /*----------------------------
   Output from the external Web Service:
   <ORDER>
       <ORDERNUM>...</ORDERNUM>
       <ORDERDATE>...</ORDERDATE>
       <INVOICENUMBER>...</INVOICENUMBER>
   </ORDER>
  -----------------------------*/
 //Define paths used for parsing
 public static final String ORDERDATE_ELEMENT = "ORDER/ORDERDATE";
 public static final String INVOICENUMBER_ELEMENT = "/ORDER/INVOICENUMBER";
 public Map getOrderDetails(Map params) throws Exception
 {
    Map<String,String> order = new HashMap<String,String>();
    String orderNum = (String) params.get("orderNum");
    if(orderNum != null && orderNum != "" )
    {
       /** Return test values **/
       if (orderNum.equals("1")) {
          order.put("orderDate", "11 Jan 2011");
          order.put("invoiceNumber", "1111111111");
          return order;
       } else if (orderNum.equals("2")) {
          order.put("orderDate", "22 Feb 2022");
          order.put("invoiceNumber", "2222222222");
          return order;
       } else if (orderNum.equals("3")) {
          // On error, return an empty data set
         return order;
       }
       /** Execute the Web Service **/
       String orderDate = "";
       String invoiceNumber = "";
     
       HttpConnection con = 
          new HttpConnection(CONSTANTS.HTTP.METHOD.GET, WS_URL+orderNum);
       int code= con.execute(); 
       String response = con.getResponse();
       try
       {
          // Parse the response to retrieve order date and invoice#.
          XPathFactory factory = XPathFactory.newInstance();
          XPath xPath = factory.newXPath();
          
          orderDate = xPath.evaluate(
            ORDERDATE_ELEMENT, 
            new InputSource(new StringReader(response))
            );
          invoiceNumber = xPath.evaluate(
            INVOICENUMBER_ELEMENT,
            new InputSource(new StringReader(response))
            ); 
          
          order.put("orderDate", orderDate);
          order.put("invoiceNumber", invoiceNumber);
       }
       catch(XPathExpressionException xpe) 
       {
          Logger.info(xpe.getCause(), "HttpConnection");
          showMessage("Parse of Web Service data failed. Check debug log.");
       }
    }
    return order;                         
 }     

} </syntaxhighlight>

10.2 Invoke a Web Service from a JSP Page

This JSP page displays a simple form that uses the Web Service Proxy defined above. When the user clicks [Search], retrieved data is displayed:

WebServiceJspFormExample.png

Notes:

  • JavaScript embedded in the page uses the platform's REST exec API to invoke the getOrderDetails() method.
  • The Input XML sent to that method looks like this:
<syntaxhighlight lang="xml" enclose="div">

<platform>

  <execClass>
     <clazz>com.platform.yourCompany.orders.WebServiceProxy</clazz>
     <method>getOrderDetails</method>
     <orderNum>...</orderNum>
  </execClass>

</platform> </syntaxhighlight>

  • The jQuery .ajax method is used to make the REST request and to process the returned data.
  • The structure of the returned data is defined by the platform's REST exec API. (It's in JSON format, at the request of the AJAX call). The part of the structure we are concerned with here looks like this:
<syntaxhighlight lang="xml" enclose="div">

{"platform": {

 "execClass": {
   ...
   "orderNum": "..."
   "orderDate": "...",
   "invoiceNumber": "...",
 },
 ...

}} </syntaxhighlight>

JSP Page
<syntaxhighlight lang="xml" enclose="div">
Order#: <input type="text" id="orderNum" name="orderNum" size=10/> <input type="button" id="submit" name="submit" value="Search"/>





<script type="text/javascript">

 $("#submit").click( function(){
   var orderNum= $('#orderNum').val();
   var inputXML = "<platform><execClass>"
                + "<clazz>com.platform.yourCompany.orders.WebServiceProxy</clazz>"
                + "<method>getOrderDetails</method>"
                + "<orderNum>" + orderNum + "</orderNum>"
                + "</execClass></platform>"; 
  try {
   /* jQuery AJAX call to invoke REST API and process returned data */
   $.ajax({
     type: 'POST',
     url: '/networking/rest/class/operation/exec',
     contentType: 'application/xml',
     Accept:'application/json',
     data: inputXML,  
     dataType: 'json',
     success: function(data) {
       /* Clear the results table. (jQuery method that removes */
       /* element content, but not the element or its attributes.) */
       $('#result').empty();  
    
       /* Size the table and make it visible */
       $('#result').attr('border', '1');
       $('#result').attr('style', 'width:300px');
       
       var orderNum = data.platform.execClass.orderNum;
       if (orderNum) {
         var orderDate = data.platform.execClass.orderDate;
         var invoice = data.platform.execClass.invoiceNumber;
         /* Add header rows and table data */
         $('#result').append(

'Order# ' +'Order Date' +'Invoice Number'

         );
         $('#result').append(

''+orderNum+'' +''+orderDate+'' +''+invoice+''

         );     	
       } else {
         /* Call succeeded, but returned no data */

$('#result').append('No data found for that order.');

       }
     } /* End of success function */
   }); /*End of ajax call*/
  } catch (e) {
    // JavaScript errors aren't recorded in the debug log.
    // Make sure we see them.
    alert(e);
  };
 }); /* End of processing function */          

</script> </syntaxhighlight>

10.3 Add a Button to a Form

The JavaScript shown here adds a button to a standard object Form:

FormLookupButtonAdded.png

The standard form is shown on the left. ON LOAD JavaScript added to it injects the button, producing the form on the right. That code also defines the button's operation, so that clicking it calls the getOrderDetails method defined earlier.

Notes:

  • The current form is referenced as _sdform.
  • As in the previous example, an AJAX call is used to invoke the method in the Web Service Proxy.
  • Form fields are then populated with the returned values.
Learn more: Form Scripts

ON LOAD JavaScript:

<syntaxhighlight lang="javascript" enclose="div">

// Get a pointer to the current form var form = _sdForm

try { /*

* Find the order number field, modify it's CSS properties,
* and add a search button to the element it's contained in.

* (Each label and field is contained in it's own

. The
*  .parent() function accesses it, and the button is added at the end.)
*/

form.find("#orderNum").css( {'width': '50%',"margin-bottom":"0px"}); form.find("#orderNum").removeClass("text_box_case"); form.find("#orderNum").parent().append(

  "<input type='button' id='lookup_order' name='lookup_order' value='Lookup Order'" 
+ "style='background: none repeat scroll 0 0 #EBEBEB;box-shadow: 0 0 0 0 #888888 inset;"
+ " height: 26px;margin-top:-0px; padding: 0 8px 0 6px;"
+ " margin-right:5px;margin-left:5px;' >" 
  ) ;

/*

* Add an anonymous function to the button to invoke the WebServiceProxy method. 
*/

form.find("#orderNum").parent().find("#lookup_order").click(

 function() 
 { 
   // Create the structure to send to the proxy method
   var orderNum= getTextFieldValue(_sdForm, "orderNum"); 
   var inputXML = "<platform><execClass>"
                + "<clazz>com.platform.yourCompany.orders.WebServiceProxy</clazz>"
                + "<method>getOrderDetails</method>"
                + "<orderNum>" + orderNum + "</orderNum>"
                + "</execClass></platform>"; 
   // Use jQuery's ajax method to invoke the REST API
   $.ajax({
     type: 'POST',
     url: '/networking/rest/class/operation/exec',
     contentType: 'application/xml',
     Accept:'application/json',
     data: inputXML, 
     dataType: 'json',
     success: function (data) 
     {
       // Call succeeded.
       if (data.platform.execClass.invoiceNumber) {
          // Copy data to form fields.
          setTextFieldValue(_sdForm, "invoice_number",
                            data.platform.execClass.invoiceNumber);
          setTextFieldValue(_sdForm, "order_date_1462329883",
                            data.platform.execClass.orderDate);
       } else {
          alert("No order found.");
       }  
     } // End of success function
   }); // End of AJAX call
   // We're done. Don't submit the input form to the web service.
   // (We already did that, and it doesn't know what to do with form data.)
   return false;
 }); // End of button function

} catch (e) {

  // JavaScript errors aren't recorded in the debug log.
  // Make sure we see them.
  alert(e);

}; </syntaxhighlight>

11 Search and Update

To update a record, this example follows these steps:

In this example, the Contact record that was created in the Add a Contact Recordexample is updated by associating it to the Account record created in the Add an Account Record example.

11.1 Search for Account and Contact Records

Follow these general steps to search for records:

  1. Call searchRecords
  2. Check the result
  3. Process the returned records

First, create a variable to hold the record identifier.

<syntaxhighlight lang="java" enclose="div">

String contactRecID = null; </syntaxhighlight>

When a record is added database, the platform assigns it a unique record identifier. The value of a record identifier is opaque and is of no interest to the typical user. However, several Java API calls use the recordID parameter.

To get a record identifier, request the record_id field when making a searchRecords call. Find more information about arguments in searchRecords.

This example calls searchRecords for the CONTACT object. In order, the parameters to searchRecords in this example are:

  • name of the object
  • names of the fields to retrieve which includes record_id
  • filter string (in this case, the filter string specifies that last_name contains "Smith" (the same as for the contact record previously created)
<syntaxhighlight lang="java" enclose="div">

Result contactSearchResult = Functions.searchRecords("CONTACT", "record_id,first_name,last_name,email", "last_name contains 'Smith'"); </syntaxhighlight>

11.1.1 Check the Result

The result code for searchRecords works a little differently than for addRecord.

Debug Example for searchRecords

This example checks the return code by calling Result.getCode, and takes some action, based on the return code:

  • Return codes:
  • less then zero (<0); the call is not successful
  • equal to zero (=0); no records found
  • greater than zero (> 0); successful and the value of the return code is the number of records returned

This code sample assigns the result code to a variable, which then defines the following action:

  • If not successful, an error dialog is displayed
  • If the code is zero, then a message is written to the debug log
  • If successful, then the code is the number of records returned
<syntaxhighlight lang="java" enclose="div">

int contactSearchCode = contactSearchResult.getCode();

if(contactSearchCode < 0) {

   String msg = "Error searching CONTACT object";
   Logger.info(msg + ":\n" + contactSearchResult.getMessage(), "Search");
   Functions.throwError(msg + ".");                     

} else if(contactSearchCode == 0) {

   Logger.info("Contact:No records found using the search function in CONTACT.",
               "Search");

} else {

   // If the code "falls through" here, it means records were returned 
   // that need to be processed; use the value of the return code in 
   // the next section ...

} </syntaxhighlight>

11.1.2 Process the Returned Records

In earlier examples, the Parameters object was shown to hold name-value pairs for fields for when the addRecord call is made.

The searchRecords call uses the Parameters object, but in a different way - the searchRecords call returns the set of fields that were requested for each record that matches the search criteria as an array of Parameters objects.

To process each Parameters object in the search results, create an instance of ParametersIterator and then specify ParametersIterator.hasNext as the expression of a while loop.

The example resumes with the code "falling through" when checking the result code for searchRecords, meaning that the result code is greater than zero, which is the number of records returned.

The following code sample:

  • Creates an instance of ParametersIterator by calling Result.getIterator
  • Sets up a while loop with ParametersIterator.hasNext as the expression to evaluate at each iteration; Within the while loop, the code will:
  • Creates an instance of Parameters by calling ParametersIterator.next
  • Calls Parameters.get, specifying record_id as the parameter to get the value of the record identifier field which is assigned to a variable named contactRecordId
  • Makes a Java API getRecord call with these parameters:
  • Object identifier
  • List of fields to get
  • The record identifier which is contactRecordId in this case
  • Makes a nested call to Result.getParameters to get the value of the first_name field; Checks if it is equal to "John". If so, the contactRecordId is assigned to the variable named contactRecID and the code breaks out of the loop; The contactRecID variable is used later when calling updateRecord, and sendEmailUsingTemplate
<syntaxhighlight lang="java" enclose="div">

{

  // "Falling through" here means records were returned
  Logger.info("Search for John Smith: Number of records found using 
      search function in Contact:" + contactSearchCode, "Search");
  ParametersIterator contactIterator = contactSearchResult.getIterator();
  while(contactIterator.hasNext())
  {
     Parameters contactParams = contactIterator.next();
     String contactRecordId = contactParams.get("record_id");  
     Result getContactRecordResult = Functions.getRecord("CONTACT", 
        first_name,last_name,flag_primary_contact,description", contactRecordId);
     String firstName = (getContactRecordResult.getParameters()).get("first_name");
     Logger.info("Result from getRecord:\n" + getContactRecordResult.getMessage(),
                     "Search");
     Logger.info("Return code from getRecord:" + getContactRecordResult.getCode(),
                     "Search");
     Logger.info("First name retrieved : " + firstName, "Search");
     if(firstName.equals("John")) {
        contactRecID = contactRecordId; 
        break;
     }
  }

} </syntaxhighlight>

11.2 Relate the Account Record to the Contact Record

As objects are manipulated in the platform (added, updated, deleted), associations between objects must be maintained.

For example, when a Contact is created, it must be related to an Account:

  • In the platform user interface, objects are related using the Lookup field as described in Relating Objects Using Lookups
  • In the Java API, objects are related in code by searching for an object and then using a field value that identifies the object to set a field in a related object

Define the Relationship between Objects In this example, the code searches for an Account and then uses the Account Number to set a field in the contact. When relating a record in the Account object to a record in the Contact object, these three key-value pairs are required to define the relationship:

  • reference_type
  • related_to_id
  • related_to_name

The following code searches for an account where the name field contains the text string "Hello". The Account record is created in Add an Account Record. The code follows the same model for a Update Records: call searchRecords, check the result code, and loop to process the returned records.

The following code sample:

  • Sets up a while loop with ParametersIterator.hasNext as the expression to evaluate at each iteration. Within the while loop, the code creates two instances of Parameters:
  • The first instance is created by calling getParametersInstance and is named updateContactParams; This instance is used to update the contact record later
  • The second instance is created by calling ParametersIterator.next and is called accountParams
  • Calls accountParams.get, specifying record_id as the parameter to get the value of the record identifier field, which is assigned to a variable named accountRecordId.
  • Makes a Java API getRecord call using accountRecordId as a parameter
  • Makes a nested call to Result.getParameters to get the value of the name field
  • Checks if the name is equal to "Hello World"; If it is, the code adds some name-value pairs to updateContactParams
  • Assigns the accountRecordId retrieved in the previous account search to related_to_id (This is how record identifiers are used to relate one object to another in the Java API)
  • Makes an updateRecord call, specifying contactRecID and updateContactParams as parameters, which updated the Contact record.
  • Checks the result in the final lines of code in the same way that previous examples did.
<syntaxhighlight lang="java" enclose="div">

Result accountSearchResult = searchRecords ("ACCOUNT", "record_id,name,number,primary_contact_id",

   "name contains 'Hello'");

int accountResultCode = accountSearchResult.getCode(); Logger.info(" Account:Result message for search ALL Account Records is "

           + accountSearchResult.getMessage(), "Search");

Logger.info(" Account:Result code for search ALL Record is " + accountResultCode,

           "Search");

if(accountResultCode < 0) {

   String msg = "Account could not be retrieved";
   Logger.info(msg + ":\n" + accountSearchResult.getMessage(), "Search");
   Functions.throwError(msg + ".");

} else if(accountResultCode == 0) {

   Logger.info("Account:No records found using the search function in ACCOUNT.", 
               "Search");

} else {

   Logger.info(
     "Search for Hello World: Number of records found using search function in Account:"
     + accountResultCode, "Search");
   ParametersIterator accountIterator = accountSearchResult.getIterator();
   while(accountIterator.hasNext())
   {
       Parameters updateContactParams = Functions.getParametersInstance();
       Parameters accountParams = accountIterator.next();
       String accountRecordId = accountParams.get("record_id");	      
       Result getAccountRecordResult = 
           Functions.getRecord("ACCOUNT", "name,description", accountRecordId);
       String accountName = (getAccountRecordResult.getParameters()).get("name");
       Logger.info("Result from getRecord on ACCOUNT:\n" 
                   + getAccountRecordResult.getMessage(), "Search");
       Logger.info("Return code from getRecord on ACCOUNT:" 
                  + getAccountRecordResult.getCode(), "Search");
       if(accountName.equals("Hello World")) 
       {
           Logger.info("Account record ID:" + accountRecordId, "Update");
           updateContactParams.add("description", "Updating Contact");
           updateContactParams.add("reference_type", "Account");
           updateContactParams.add("related_to_id", accountRecordId);
           updateContactParams.add("related_to_name", accountName);
           Logger.info("Updating contact record with id:" + contactRecID, "Update");
           Result contactUpdateResult = 
               Functions.updateRecord("CONTACT", contactRecID, updateContactParams);
           if(contactUpdateResult.getCode() == 0) 
           {
              Logger.info("Account:Contact of the Account record updated successfully\n"
                  + contactUpdateResult.getMessage(), "Update");
           }
           else
           {
               String msg = "Error updating contact";
               Logger.info(msg + ":\n" + contactUpdateResult.getMessage(), "Update");
               Functions.throwError(msg + ".");
           }  
       }           
   }

} </syntaxhighlight>