AR System APIs Quick Start Java

Version 21
    Share This:

    << AR System APIs and Integration Interfaces Overview

     

    Comparison to other languages >> C Java Driver Perl C# VB

    Description

    BMC has published JAVA API samples with the AR System documentation, it is available from the BMC Documentation site at the following URL:

    https://docs.bmc.com/docs/display/public/ars91/Java+API+sample+code+for+managing+BMC+Remedy+AR+System+records

     

    It details out sample methods for the following actions:

     

    • Connecting to an AR System;
    • Creating an entry in a form using given field values;
    • Modifying the short description field on a specific entry;
    • Retrieving an entry by its Entry ID and print out the number of fields in the entry.  For each field in the entry, print out the value and the field info (name, id and the type);
    • Retrieving entries from a form using the given qualification.  With the returned entry set, print out the ID of each entry and the contents in its Short Description field

     

    In addition to the BMC provided samples in the link above, here is a small part of a larger API tutorial I've been working on... I apologize for the format as my document template doesn't play nice being copied into BMC Communities documents so there are spacing / formatting issues here and there.  I'll try and clean it up overtime.  Note that at the bottom of this document is an AR definition with a sample I use for the various samples.  Full disclosure, I am only a recent hobbyist Java programmer so while I try and explain things, my terminology and understanding may not be correct so please keep this in mind if I make glaring bad mistakes on the Java side (both in design patterns and explanations).

     

    BEGIN TUTORIAL

     

    Since version 7.1 of the AR System, the Java API has been native, as opposed to previously where it was mostly a Java wrapper around the C API utilizing a JNI layer.  We will focus only on the native Java API which means full compatibility back to AR System 7.1; support for against older versions of AR System will not be covered here.

     

    The Remedy API is consistent with an object-orientated design, meaning that all Remedy objects translate to Java Classes & Objects. What this means is that there is not a lot of “Magic” that needs to be learnt on leveraging the API, just knowing how everything fits together typically can help achieve most use-cases.

     

    In order to begin using the BMC Java API, you must first have a development environment properly setup and configured.  If you required help setting up a development environment please follow the following link:

     

    AR System APIs Quick Start Java - Dev Environment Setup

     

    Once you have your environment setup or if you are already setup, you can continue the tutorial.

    ARServerUser Class

     

    The most important Class within the Remedy API is the ARServerUser Class.  This one class contains your connection to the AR System as well as provides methods for performing most general activities you may desire.  Most of the methods are for either getting or setting data, workflow, settings, etc.  When working with other objects, there typically is two approaches the API will provide which include methods for simply returning either a single object or a List of objects to be used however the programmer may want.  In later sections we will review use-cases for each and when best to use methods that return Lists of Strings that represent objects vs. those who return List of Objects directly as each have their own pros and cons.

     

    In order to instantiate an ARServerUser object, you must first import the Class into your Java project.  To do this, assuming the libraries are all properly registered in your IDE, simply add the following line to the beginning of your Class:

     

    import com.bmc.arsys.api.ARServerUser;
     

     

    There are several constructors available for instantiating your ARServerUser object, here we overview the more common constructs used:

     

    ARServerUser() – Used if you are going to add the required authentication info via methods

    ARServerUser(String user, String password, String locale, String server)

    ARServerUser(String user, String password, String authentication, String locale, String server, int serverPort)

     

    Here is an example of creating an ARServerUser object using the first example above which requires some class methods to set the authentication info rather than providing it as part of the constructor:

     

    import com.bmc.arsys.api.ARServerUser;
    
    public class Tutorial {
        public static void main(String[] args) {
            ARServerUser ctx = new ARServerUser();
            ctx.setServer("ServerName");
            ctx.setUser("Username");
            ctx.setPassword("Password");
        }
    }
     

     

     

    The example above will create the necessary ARServerUser object which will then establish a connection to the AR System.  In order to verify if the connection is correct, you can call the ARServerUser.verifyUser() method which will return an ARException if there is any issue logging in.  It is good practice to always verifyUser() immediately after instantiating the ARServerUser object prior to attempting to call any other methods.

    Exception Handling

     

    Because the verifyUser() method described above can throw an ARException, here we will overview the exception model leveraged by the ARSystem.

     

    In order to simplify the exception handling in the API, the only exception ever thrown by the Remedy API directly is ARException.  To complete our example of constructing our ARServerUser object and verifying that we indeed are able to successfully connect to our target AR System, we can expand our example to the following which includes an additional import statement for the ARException Class as well as an additional block of code for performing the verifyUser() method:

     

    import com.bmc.arsys.api.ARException;
    import com.bmc.arsys.api.ARServerUser;
    
    public class Tutorial {
        public static void main(String[] args) {
            ARServerUser ctx = new ARServerUser();
            ctx.setServer("ServerName");
            ctx.setUser("Username");
            ctx.setPassword("Password");
            try {
                ctx.verifyUser();
            } catch (ARException e) {
                System.out.println(e.getMessage());
            }
        }
     

     

    Let us review this code; first we instantiate our ARServerUser Object and then set it’s authentication credentials via provided methods, we then make a call to the verifyUser() method that will throw an ARException if the user cannot authenticate to the AR System for any reason.  The actual reason for the failure will be provided in the ARException and so we will print the message to the console if this happens for troubleshooting purposes.  As with any exception handling in Java, you must encapsulate the methods that can throw exceptions within a Try/Catch block or add the exception to the methods signature to be handled elsewhere.

     

    The above program source as a working template, we can now look at creating our first working program that will give us some useful information.

     

    Constructing Our First Program

    Overview

    In the above section, we reviewed the minimum amount of code to instantiate an ARServerUser object, which communicates to the AR System, as well as verify that we have successfully logged in with the verifyUser() method.  Let us now put this together into a program that is a bit closer to what a real program might look like by way of using variables and utility methods and having it perform some key activities to output information about the server we are connected to.

     

    The following complete program will connect to an AR System, verify we are logged in correctly and then query the server for several pieces of information using methods defined within the ARServerUser Class.  For this next bit, make sure you are using a user that has Administrator permission as the call to get the list of all connected users will require admin access.

     

    The specific information that we will output in this program includes the following:

    • Output the AR Server Name we are connected to
    • Output the version string for the connected AR Server
    • Output a list of all currently connected users to the AR System as well as when their last access time was

     

    You’ll notice we declare the ARServerUser object at the class level instead of in main like our previous example; this is so that we can leverage it in methods outside of our main method.  This program also highlights several new methods and Objects to interact with which include the UserInfo Object as well as some helper methods for converting and displaying dates properly.  Because our method call to get all the connected users will return a List<UserInfo> object, we need to iterate through the entries one by one to output the details.

     

    Below is our first program.  For this to run in your own environment, simply update the following variables with your own AR Server authentication information and it should run just fine.

    • serverName
    • userName
    • userPassword

     

    Sample Code

     

    import com.bmc.arsys.api.ARException;
    import com.bmc.arsys.api.ARServerUser;
    import com.bmc.arsys.api.Constants;
    import com.bmc.arsys.api.UserInfo;
     
    public class Tutorial {
        private static ARServerUser ctx;
    
        public static void main(String[] args) {
            // Setup my authentication credentials
            String serverName = "TOR01499";
            String userName = "appadmin";
            String userPassword = "obsappadmin";
    
            // Instantiate my ARServerUser Object
            ctx = new ARServerUser();
            ctx.setServer(serverName);
            ctx.setUser(userName);
            ctx.setPassword(userPassword);
    
            // Verify that the authentication information provided is valid
            connectionTest();
    
            // Output some key information about the connected AR Server
            showARSystemDetails();
    
            // Log out from AR Server when completed
            ctx.logout();
        }
    
        private static void connectionTest() {
            try {
                ctx.verifyUser();
            } catch (ARException e) {
                System.out.println(e.getMessage());
            }
        }
    
        private static void showARSystemDetails() {
            System.out.println("Connected to AR Server: " + ctx.getServerInfoStr());
            System.out.println("AR Server version: " + ctx.getServerVersion() + "\n");
            System.out.println("List all connected users and last accessed time");
            try {
                for (UserInfo user : ctx.getListUser(Constants.AR_USER_LIST_CURRENT)) {
                    System.out.println("   " + user.getUserName() + " - "
                        + user.getLastAccessTime().toDate());
                }
            } catch (ARException e) {
                System.out.println(e.getMessage());
            }
        }
    }
     

     

     

    Let’s break the new areas of this program down to understand what is happening, specifically the showARSystemDetails() method call where we introduce two (2) new Classes as well as a few new methods.

     

    ARServerUser.getServerInfoStr()

    This method provides a String value that represents the Server we are connected to.

     

    ARServerUser.getServerVersion()

    This method provides a String value that represents the version of the AR server, it may be different in format from version to version as BMC’s chosen representation of version numbers have evolved over the years however it will always give you a full version String.  If you are more interested in using the version numbers programmatically to execute code only if you are connected to a certain version, there are additional methods to return specific version information such as:

     

    • ARServerUser.getServerVersionMajor()
    • ARServerUser.getServerVersionMinor()
    • ARServerUser.getServerVersionPatch()

     

    Experiment with them to understand their output.

     

    ARServerUser.getListUser(Constants.AR_USER_LIST_CURRENT)

    This method provides a List<UserInfo> object that contains information for all user accounts.  The method is overloaded to provide the ability to specify what type of users you wish to include.  In our example we provided an overriding parameter.  The list of possible parameter values include:

     

    • Constants.AR_USER_LIST_REGISTERED
    • Constants.AR_USER_LIST_CURRENT
    • Constants.AR_USER_LIST_MYSELF

     

    You’ll notice that we make use of the Constants Class which is where most constants and enums are defined for use throughout the API. Constants are sometimes not very well documented as to what their intended purpose is so sometimes it is beneficial to reference the C API header file (ar.h) which can be found typically here:

     

    C:\Program Files\BMC Software\ARSystem\Arserver\api\include\

     

    When we iterate through the List of UserInfo objects, there are several methods available from the UserInfo class.  These methods give us some high level details about the users. In our example we use the UserInfo.getUserName() method to give us the username as well as the UserInfo.getLastAccessTime() to give us a Timestamp object that represents the last activity time of the user.  Because a Timestamp object is an instance of the Timestamp class, it isn't human readable however it contains a method called Timestamp.toDate() which will translate it into a human readable date format.

    Interacting with Data

    Now that we are familiar with the basics of creating a program to interact with an AR System, let’s look at something that may be a bit more useful, interacting with and interpreting data.  We will look at what the following types of interactions with the system and provide working examples for all:

    • Creating data in a AR forms
    • Querying & Modifying data from AR forms
    • Understanding Data Types
    • Understanding Limitations & Best Practices for Data

     

    Creating Data in AR Forms

    In order to understand how to create data in AR forms, there are a couple of key objects that must be understood first.

     

    The first of these objects is the Entry object; the Entry object is a representation of the full dataset of a record in an AR form and is implemented as an extension to the LinkedHashMap object so it will contain key/value pairs where the key is the FieldID of a field on the AR form and the value is a Value object which contains the actual data to be placed in the field.

    As mentioned above, the second key object to understand is the Value object; the Value object can store any of the data types required by any valid AR field.

     

    So essentially, an Entry object contains multiple key/value pairs that make up the full data of a record in an AR form.  If we look at a simple example, we will see how this is implemented.

     

    To begin the real tutorial, please import AR definition file that was included with this document (apitutorial.def).  It contains an AR form called API:TUTORIAL with a single field of every possible data type.  Also please have a copy of the def file at the root of your project directory as we will also use it as a sample attachment to load into the API:TUTORIAL form itself.

     

    Since we are in an API tutorial document, to accomplish the loading of the def file via API  (don’t worry, we will review and cover in detail in later sections) you can run the following method:

     

    private static void importDef() {
        String defFile = "apitutorial.def";
        try {
             ctx.importDefFromFile(defFile, Constants.AR_IMPORT_OPT_OVERWRITE);
             System.out.printf("Successfully imported file: %s\n", defFile);
        } catch (ARException e) {
             System.out.println(e.getMessage();
        } catch (IOException e) {
             System.out.println(e);
        }
    }
     

     

    In this example, we will create a new record in the API:TUTORIAL form that we imported.  Note that we will no longer include full working examples, only specific methods to highlight functionality being discussed, in order to execute this program, use the template from the “Creating our First Program” section and simply add this method to it and call it from the main method.

     

    We first need to import the two new classes discussed above:

     

    import com.bmc.arsys.api.Entry;
    import com.bmc.arsys.api.Value;
     

     

    And now our sample method for an entry in our API Tutorial form

     

    private static void createAPITutorialSampleRecord() {
        try {
            String schema = "API:TUTORIAL";
            String aName = "apitutorial.def";
            String aPath = "apitutorial.def";
            AttachmentValue aValue = new AttachmentValue(aName, aPath);
          
            Entry entry = new Entry();
            entry.put(700000000, new Value("This is a Character Field Entry")); // Character Field
            entry.put(700000001, new Value("This is a Diary Field Entry"));     // Diary Field
            entry.put(700000002, new Value("5/5/2013 5:23:12 PM"));             // DateTime Field
            entry.put(700000003, new Value("5/5/2013"));                        // DateOnly Field
            entry.put(700000004, new Value("5:23:12 PM"));                      // TimeOnly Field
            entry.put(700000005, new Value("56.77 CAD"));                       // Currency Field
            entry.put(700000006, new Value("32"));                              // Integer Field
            entry.put(700000007, new Value("32.4566"));                         // Real Field
            entry.put(700000008, new Value("32.56"));                           // Decimal Field
            entry.put(700000009, new Value("Value 1"));                         // Selection Field
            entry.put(700000010, new Value("Value 1"));                         // Radio Field
            entry.put(700000011, new Value("Selected"));                        // CheckBox Field
            entry.put(700000013, new Value(aValue));                            // Attachment Field
            String result = ctx.createEntry(schema, entry);
            System.out.printf("Entry %s created\n", result);
        } catch (ARException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    Let’s break this down to understand what is going on here.  First we define which AR form we want to target, which in this case is the “API:TURORIAL” form.  Then we instantiate a new empty Entry object and begin to populate it with data.  Because we have data fields of all types in our API:TUTORIAL form, we can see the various formats for the Value objects, we will explore these further in a subsequent section.  You will notice that we are also including an attachment, that is looking for the apitutorial.def file in the root of the project folder, you can change this path or point to a different path if you want so long as the aPath variable is set properly, e.g. if you point to a different folder, it could look like this:

    “C:\\yourpath\\apitutorial.def" – Notice the double backslash when dealing with directories

    We can see that because the Entry object is an extension to the LinkedHashMap object, that we use common methods like Entry.put() and Entry.remove() to manipulate the data within the Entry object.  In our case, we simply want to populate our fields to successfully create an entry in the API:TUTORIAL form so understanding all the field requirements; we build out our Entry object.

    Notice that each Entry.put() is actually using FieldID as the key and a new Value object as the value.  We’ll get into the specifics of each type of Value object in a later section, for now all you need to know is that once our Entry object is fully populated, we can then call a method from our ARServerUser object to attempt to create the object.  The method is conveniently called ARServerUser.createEntry() which will take exactly two (2) parameters.

    The parameters are:

    • String schemaName
    • Entry entry

    The ARServerUser.createEntry() call will either return the Request ID if the record was successfully created, or it will throw an ARException which we can catch and output the error.  If not error is thrown, in our example we will output a success message to the Console.  Errors could occur in this method if not all mandatory fields were met or if the Value object passed in for one of the entries in the Entry object was not of the correct type for the field being mapped to, etc.

    Please execute this method so that data is inserted into the API:TUTORIAL form as we will be using this sample record through the remainder of this chapter.

    Querying data from AR Forms

     

    Creating data is very straight forward however being able to query and then possibly modify the results is a bit more involved and here I will outline the various techniques and methods required to perform these actions.

     

    There are two (2) primary ways to query for data within the Remedy API, the first is to simply query for a single record based on the Request ID and the second is to perform a query using some qualification which may return zero (0) or more results.  We will look at the first scenario first as it is the simplest.

     

    Single Record Query

    In the following example, we will query the “API:TUTORIAL” form for the record we created earlier.

     

    private static void getAPITutorialSampleEntry() {
        String schemaName = "API:TUTORIAL";
        String entryID = "000000000000001";
        try {
            Entry record = ctx.getEntry(schemaName, entryID, null);
            for (Integer i : record.keySet()) {
                System.out.println(record.get(i));
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    In the example above, we define our target AR form and the Request ID of the record we wish to return.  We then perform a new method called ARServerUser.getEntry() which will return of an Entry object containing all the data related to our desired record.  This method has three (3) parameters, they are:

    • String schemaName
    • String requestID
    • int[] fieldList

    Because the Entry object created is an extension of the LinkedHashMap, instead of simply printing the results to the screen in one long line, we can iterate by the keys (fieldIDs) to print out all the values.  You will notice that the output of this sample will simply output the raw data with no special attention to different data types so for example, selection fields will show only the enum ID for the value rather than the actual value (e.g. fieldID 700000009, it will display 0 instead of “Value 1”).  We will improve upon this example in the next sections on understanding data types.

     

    Query by Qualifications

    This next example will introduce two (2) new Classes and methods which include the parsing of qualifications as well as the ability to return a List of Entry objects rather than a single Entry object.

     

    The creation of a qualification to be used in querying an AR form starts with the qualification text itself which should be encapsulated in a String variable.  The qualification string can be either in DB Name field name listing or in Field Label listing (similar to Advanced Search qualifications in the BMC Clients such as User Tool and Mid-Tier).  Depending on which string type you have selected to use will influence which overloaded ARServerUser.parseQualification() method you must use.  The simplest explanation is that if you want to use the Field Label names to list the fields, you must also know the view name to include as a parameter (or let it use the default view and hope the label is correct on that view by passing empty string).

     

    The end result of performing an ARServerUser.parseQualification() call is the creation of a QualifierInfo object that is the systems internal representation of the human readable query string provided. This QualifierInfo object will be used the next method we will discuss which will provide us with a List of Entry objects.

     

    The ARServerUser.getListEntryObjects() method will allow us to return a List of Entry objects based upon the results of the QualifierInfo object that will pass to it.  It has an overloaded version where instead of passing a QualifierInfo object, you can instead provide it with a List of Strings that represent Request IDs however we will not overview this version here.

     

    There are many parameters to this method so we will overview them here and identify which are important at this stage:

    • String formName
    • QualifierInfo qualification
    • int firstRetrive
    • int maxRetrive
    • List<SortInfo> sortList
    • int[] fieldIDs
    • Boolean useLocale
    • OutputInteger nMatches

     

    To better highlight the capabilities of returning multiple entries, please run the previous createAPITutorialSampleRecord () method several times to create a larger dataset.

     

    We need to first import the new Class discussed above:

    import com.bmc.arsys.api.QualifierInfo;

     

    Now here is our sample code:

     

    private static void getAPITutorialEntries() {
        String schemaName = "API:TUTORIAL";
        String queryString = "'Character Field' LIKE \"This is a%\"";
        try {
            QualifierInfo qual = ctx.parseQualification(schemaName, queryString);
            List<EntryListInfo> eListInfos = ctx.getListEntry(schemaName, qual, 0, 0, null, null, false, null);
            for (EntryListInfo eListInfo : eListInfos) {
                Entry record = ctx.getEntry(schemaName,eListInfo.getEntryID(), null);
                for (Integer i : record.keySet()) {
                    System.out.println(record.get(i));
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    As we can see above, our queryString is human readable and leveraging all the same capabilities of searching in an AR form directly.  This queryString gets encoded to a QualifierInfo object qual by way of the ARServerUser.parseQualification() method.

    Using our QualiferInfo object, we can query the AR System using the ARServerUser.getListEntry() method which will return a List of EntryListInfo objects, which contain minimal information about each entry, that matched our qualification.  We can then do some simple iteration to cycle through the results and for each result, we call out ARServerUser.getEntry() method similar to our first example as we can get the Request ID from the EntryListInfo object by way of the EntryListInfo.getEntryID() method.   Now that we have our Entry object we simply output the values the same way as previously seen by iterating the keySet of the Entry.

    Understanding Data Types

    There are a few different data types utilized within the AR System which each have their own object representation within the Remedy API.  Different types of objects include the following which are all extensions of the Field Class:

    • CharacterField
    • IntegerField
    • DecimalField
    • RealField
    • DiaryField
    • SelectionField
    • AttachmentField
    • CurrencyField
    • DataOnlyField
    • TimeOnlyField
    • DateTimeField

     

    We will review each of these Objects one by one and describe how to properly extract the data value in the correct format for each Object type.

     

    CharacterField

    The CharacterField object is a very simple object that requires no special code to extract the value, simply printing the Value will provide the desired output.  The exception is for the Status History Field (Field ID 15).  For this we must handle it separately.

     

    The following example will output all CharacterFIeld values as well as a special case when that CharacterField is a StatusHistory.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.Field;
    import com.bmc.arsys.api.CharacterField;
    import com.bmc.arsys.api.StatusHistoryItem;
    import com.bmc.arsys.api.StatusHistoryValue;
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntryCharacterField() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof CharacterField) {
                        if (fieldID != 15) {
                            System.out.println(val.toString());
                        } else {
                            StatusHistoryValue shVal = StatusHistoryValue.decode(val.getValue().toString());
                            StringBuilder shBuilder = new StringBuilder();
                            if (shVal != null) {
                                for (StatusHistoryItem shItem : shVal) {
                                    if (shItem != null) {
                                        shBuilder.append(shItem.getTimestamp().toDate().toString());
                                        shBuilder.append(“\u0004”);
                                        shBuilder.append(shItem.getUser());
                                        shBuilder.append(“\u0003”);
                                    } else {
                                        shBuilder.append(“\u0003”);
                                    }
                                }
                                System.out.println(shBuilder.toString());
                            }
                        }
                    }
                }             
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    You will notice that right off the bat that we are using a different technique here then previously seen. Here we actual test the type of Field and then take action accordingly.  Here we have leveraged the ARServerUser.getField() method to return the Field object, we will review this call and others related to AR Objects in the next Chapter so for now simply accept that by doing this we can easily perform various actions based on the type of information contained within the Value.

     

    Our first test makes sure we are working on an instance of the CharacterField class and then checks if we are on FieldID 15 (Status-History).  If we are not the status history we simply print the value as it is a normal String.  If however we are on the Status History, we must programmatically construct the output which includes special characters.  To accomplish this, we first cast the Value into an instace of the StatusHistoryValue class and then we use a StringBuilder object to create out output.

     

    The StatusHistoryValue instance will contain a List of StatusHistoryItem instances which we can iterate through in order to build our output.  We first extract the Timestamp (we will review this data type later), and then the username of the user who made the status change and add them to our StringBuilder object.  For now you don’t need to understand the purpose of the special characters, just make sure they are included, it is required if ever you use this code to export into ARX or CSV format for future importing with BMC Import utilities.

     

    IntegerField

    The Integer object is a very simple object that requires no special code to extract the value, simply printing the Value will provide the desired output.

     

    DecimalField

    The DecimalField object is a very simple object that requires no special code to extract the value, simply printing the Value will provide the desired output.

     

    RealField

    The RealField object is a slightly more complex object however also does not require any special code to extract the value, simply printing the Value will provide the desired output.

     

    DiaryField

    The DiaryField is a very different type of field that requires special handling in order to have usable data outputted.  Simply printing the Value of the DiaryField would show as follows (example has three (3) entries)

     

    [[User=appadmin,Time Val=[Timestamp=1370002884],Text=Here is sample data], [User=appadmin,Time Val=[Timestamp=1370003221],Text=I have added more information], [User=appadmin,Time Val=[Timestamp=1370003229],Text=And again a third time I have added information],Appended Text=<null>]

     

    As you can see, the DiaryField object is actually made up of a combination of information.  For each of the three (3) entries in the Diary field I have made, there is a new element added which includes the text that was input as well as the username of the user who submitted it and the Timestamp when it was committed.  The combination of all three (3) entries is an instance of a DiaryListValue object which contains individual DiaryItem objects.

     

    In order for this to be a bit more user friendly when being outputted, we need to identify that the Value object is actually a DiaryListValue object so that we can perform additional actions to clean up the format and handle the Timestamp.  While for this particular object, we could do this with the Value object alone, others such as the SelectionField actually require inspection of the Field object that matches the Entry Value so we will utilize the Field object inspection technique to identify the type of Field at play.

     

    Below is some sample code that tests if the field is infact a DiaryField object, and if it is perform the needed actions to make this more human readable, it is an extension to the previous sample code on outputting an Entry object based upon an exact search for a Request ID for simplicity sake.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.DiaryField;
    import com.bmc.arsys.api.DiaryItem;
    import com.bmc.arsys.api.DiaryListValue
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntryDiaryField() {
        String schemaName = "API:TUTORIAL";
        String entryID = "000000000000001";  
        try {
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof DiaryField) {
                        for (DiaryItem dlv : (DiaryListValue)val.getValue()) {
                            System.out.println(“Created by “ + dlv.getUser() + " @ " + dlv.getTimestamp().toDate().toString() + " " + dlv.getText());
                        }
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    As we can see above, instead of just printing all the Value objects contained within the Entry object, we grab each Value object along with the actual Field object and then test if the Field object is actually an instance of a DiaryField and if it is, we cast the Value object to DiaryListValue and iterate through it’s containing DiaryItem objects to print them line by line and also format the output in a more human friendly format.

     

    Using this technique, the previously un-user friendly output will now look like this:

     

    Created by appadmin @ Fri May 31 08:21:24 EDT 2013: Here is sample data

    Created by appadmin @ Fri May 31 08:27:01 EDT 2013: I have added more information 

    Created by appadmin @ Fri May 31 08:27:09 EDT 2013: And again a third time I have added information

     

    SelectionField

    The SelectionField object requires special attention if you wish to see actual values as opposed to the enumeration IDs. For example, on the User form, the Status field (fieldID 7) has two values defined, “Current” and “Disabled”. The “Current” value has an enum ID of 0 while the “Disabled” value has an enum ID of 1.  If you simply output the Entry object you will see either 0 or 1 as opposed to Current or Disabled.

    In order to properly display the true values, similar to the DiaryField, we must find SelectionField objects and then explore it’s dependant objects.  The following Classes will need to be understood to interact with SelectionField objects:

     

    • SelectionFieldLimit
    • EnumItem

     

    Using the same context as our previous example of working with DiaryField objects, we only test for our SelectionField object instead of repeating the previous tests.  At the end of this chapter, a full example will be given that will satisfy all possible field combinations at once.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.SelectionField;
    import com.bmc.arsys.api.SelectionFieldLimit;
    import com.bmc.arsys.api.EnumItem;
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntrySelectionField() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof SelectionField) {
                        SelectionFieldLimit sFieldLimit = (SelectionFieldLimit)field.getFieldLimit();
                        if (sFieldLimit != null) {
                            List<EnumItem> eItemList = sFieldLimit.getValues();
                            for (EnumItem eItem : eItemList) {
                                if (eItem.getEnumItemNumber() == Integer.parseInt(val.toString())) {
                                    System.out.println((eItem.getEnumItemName()));
                                }
                            }
                        }
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    As we can see above, once we identify our Field object is of type SelectionField, we then cast the Field object to a SelectionFieldLimit Class and then use the SelectionField.getFieldLimit() method to return an instance of a SelectionFieldLimit object.  We then get a List of all the EnumItem objects from the SelectionFieldLimits object by using the SelectionFieldLimit.getValues() method and then simply iterate through the list to find our matching Value to the enum ID stored in the Field by using the EnumItem.getEnumItemNumber() method and comparing it to the Integer representation of the Value object. When we find the match, we print out value using the EnumItem.getEnumItem() method.

    SelectionField objects can also have alias values that are specific to a particular view on an AR form, we will cover this aspect in the next Chapter once we understand how AR form views function.

    AttachmentField

    The AttachmentField object is different from any other Field object as it stores Base64 encoded compressed data. This type of data can obviously not be outputted to the Console in any fashion that is beneficial so here we will outline how to extract an attachment into a File object and write it to the current directory.  Again, we will not replicate any of the previous Field handling code to keep the example concise.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.AttachmentField;
    import com.bmc.arsys.api.AttachmentValue;
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntryAttachmentField() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof AttachmentField) {
                        AttachmentValue aVal = (AttachmentValue) val.getValue();
                        if (aVal != null) {
                            String aName;
                            String[] aDetails = aVal.getValueFileName().split("\\.(?=[^\\.]+$)");
                            if (aDetails.length == 2) {
                                aName = aDetails[0] + "." + aDetails[1];
                            } else {
                                aName = aDetails[0];
                            }
                            int lastPos = aName.lastIndexOf('\\');
                            String aNameShort = (lastPos < 0) ? aName : aName.substring(lastPos + 1);
    
    
                            FileOutputStream fos = new FileOutputStream(aNameShort);
                            byte[] attach = ctx.getEntryBlob(schemaName, entryID, fieldID);
                            fos.write(attach);
                            fos.close();
                        }
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    The above may seem a bit overly complex however it is required to properly extract the true attachment name as well as the actual data that is associated to the AttachmentField.  Let’s break things up and look at what we are doing above.

    Firstly, after identifying that we are on an AttachmentField, we grab the Value and cast it to an AttachmentValue object.  Now that we have the AttachmentValue (which does not contain the actual data, only the metadata about the attachment), we need to find out what the Attachment’s name is in order to name our file.

    We can easily get the file name by leveraging the AttachmentValue.getValueFileName() method and in most cases this would be enough as it would provide the file name (including extension).  Where it gets a bit more complicated is in situations where the AttachmentField was loaded by an import based upon an ARX export where attachments are stored in folders below the location of the ARX file.  The BMC Import utility by default will include the name of the folder that contains the attachment in this situation and would look something like this if we inspect just the data returned from from the AttachmentValue.getValueFileName() method:

     

    API_TUTORIAL_2013-05-31_11_31_28\\somefile.ext

     

    The above is not desirable, let us understand better how we have handled this in our code above:

     

    String aName;
    String[] aDetails = aVal.getValueFileName().split("\\.(?=[^\\.]+$)");
    if (aDetails.length == 2) {
        aName = aDetails[0] + "." + aDetails[1];
    } else {
        aName = aDetails[0];
    }
    /* Remove folder path from Attachment Name */
    int lastPos = aName.lastIndexOf('\\');
    String aNameShort = (lastPos < 0) ? aName : aName.substring(lastPos + 1);
     

     

    As we can see, we only want our file to be the name of the file so we use the split() method from the standard library to get split the result on the last “.” after the double slashes into a String[] where the first element will be everything before the last “.” and the 2nd element will be the file extension.  We then find last instance of doubleslash in the first element of the String[] aName and chop it all off leaving only the actual filename.

     

    Now that we have a proper file name we can create a new File object and name it appropriately.  In order to extract the actual data that goes with the AttachmentValue object, we must use a special method called ARServerUser.getEntryBlob() which takes as parameters the schemaName, entryID and the AttachmentFields fieldID.  This method will return a byte[] object which we can then use to write to our File object.

     

    As normal with working with File objects, we need to catch a couple of additional possible Exceptions which are FileNotFoundException and IOException and also remember to close out our file stream.

     

    CurrencyField

    The CurrencyField object is a simple object type however because it stores it’s data value as a double and can support multiple currencies, there is special handling required.


    If you simply output the Value of a currency field, you will see this for a field that is populated with 56.77 CAD:

     

    [Value=56.7700000000,Currency Code=CAD,Conversion Date=[Timestamp=1370002867],Func Currency List=[[Value=56.77,Currency Code=CAD]]]

     

    As you can see, while all the details are there, it isn’t very user friendly.  To display properly 56.77 CAD, we must again inspect the object and use several key methods to extract the correct information.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.CurrencyField;
    import com.bmc.arsys.api.CurrencyValue;
    import com.bmc.arsys.api.FuncCurrencyInfo;
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntryCurrencyField() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof CurrencyField) {
                        CurrencyValue cCurrencyValue = (CurrencyValue) val.getValue();
                        if (cCurrencyValue != null) {
                            for (FuncCurrencyInfo currInfo : cCurrencyValue.getFuncCurrencyList()) {
                                System.out.println(currInfo.getValue() + “ “ + currInfo.getCurrencyCode());
                            }
                        }
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    Here we can see that once we have identified the Field is in fact a CurrencyField instance, we can cast the Value to a CurrencyValue.  Using the CurrencyValue instance, we use the CurrencyValue.getFuncCurrencyList() method to extract both the value (dollar amount) as well as the currency identifier it was saved in by leveraging both the getValue() and getCurrencyCode() methods. The result being as desired “56.77 CAD”.

    DateOnlyField

    The DateOnly object is a very simple object that requires no special code to extract the value, simply printing the Value will provide the desired output.

    TimeOnlyField

    The TimeOnly object is a very simple object that requires no special code to extract the value, simply printing the Value will provide the desired output.

    DateTimeField

    The DateTime object stores it’s value as an epoch integer timestamp.  To extract the human readable version from that requires the use of a couple of utility methods on it’s class.

     

    We need to first import some new required Classes:

     

    import com.bmc.arsys.api.DateTimeField;
    import com.bmc.arsys.api.Timestamp;
     

     

    Now here is our sample code:

     

    private static void getAPITutorialEntryDateTimeField() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof DateTimeField) {
                        Timestamp callDateTimeTS = (Timestamp) val.getValue();
                        if (callDateTimeTS != null) {
                            System.out.println(callDateTimeTS.toDate().toString());
                        }
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    We can see above that once we have identified our DateTimeField, we cast it’s value to a TimeStamp instance and then leverage it’s .toDate() method and then convert it to String for outputting.

    Full Example

    Now that we have seen pretty much each data type individually, let’s combine everything we've learnt and build a full method for outputting all data values properly from our form.  We will leverage the same technique used throughout this section and add a bit more structure to the output by including the field name along with it’s output in the following format:

     

    Field Name: Value

     

    We will need to import almost every Remedy API Class we've seen to date:

     

    import com.bmc.arsys.api.ARException;
    import com.bmc.arsys.api.ARServerUser;
    import com.bmc.arsys.api.AttachmentField;
    import com.bmc.arsys.api.AttachmentValue;
    import com.bmc.arsys.api.CharacterField;
    import com.bmc.arsys.api.CurrencyField;
    import com.bmc.arsys.api.CurrencyValue;
    import com.bmc.arsys.api.DateTimeField;
    import com.bmc.arsys.api.DiaryField;
    import com.bmc.arsys.api.DiaryItem;
    import com.bmc.arsys.api.DiaryListValue;
    import com.bmc.arsys.api.Entry;
    import com.bmc.arsys.api.EnumItem;
    import com.bmc.arsys.api.Field;
    import com.bmc.arsys.api.FuncCurrencyInfo;
    import com.bmc.arsys.api.OutputInteger;
    import com.bmc.arsys.api.QualifierInfo;
    import com.bmc.arsys.api.SelectionField;
    import com.bmc.arsys.api.SelectionFieldLimit;
    import com.bmc.arsys.api.StatusHistoryItem;
    import com.bmc.arsys.api.StatusHistoryValue;
    import com.bmc.arsys.api.Timestamp;
    import com.bmc.arsys.api.Value;
     

     

    Now here is our full example code:

     

    private static void getAPITutorialEntryFullExample() {
        try {
            String schemaName = "API:TUTORIAL";
            String entryID = "000000000000001";
            Entry entry = ctx.getEntry(schemaName, entryID, null);
            for (Integer fieldID : entry.keySet()) {
                Value val = entry.get(fieldID);
                Field field = ctx.getField(schemaName, fieldID);
                if (val.toString() != null) {
                    if (field instanceof CharacterField) {
                        if (fieldID != 15) {
                            System.out.println(field.getName() + ": " + val.toString());
                        } else {
                            StatusHistoryValue shVal = StatusHistoryValue.decode(val.getValue().toString());
                            StringBuilder shBuilder = new StringBuilder();
                            if (shVal != null) {
                                for (StatusHistoryItem shItem : shVal) {
                                    if (shItem != null) {
                                        shBuilder.append(shItem.getTimestamp().toDate().toString());
                                        shBuilder.append("\u0004").append(shItem.getUser().append("\u0003");
                                    } else {
                                        shBuilder.append("\u0003");
                                    }
                                }
                                System.out.println(field.getName() + ": " + shBuilder.toString());
                            }
                        }
                    } else if (field instanceof SelectionField) {
                        SelectionFieldLimit sFieldLimit = (SelectionFieldLimit)field.getFieldLimit();
                        if (sFieldLimit != null) {
                            List<EnumItem> eItemList = sFieldLimit.getValues();
                            for (EnumItem eItem : eItemList) {
                                if (eItem.getEnumItemNumber() == Integer.parseInt(val.toString())) {
                                    System.out.println(field.getName() + ": " + eItem.getEnumItemName());
                                }
                            }
                        }
                    } else if (field instanceof CurrencyField) {
                        CurrencyValue cCurValue = (CurrencyValue) val.getValue();
                        if (cCurValue != null) {
                            for (FuncCurrencyInfo curInfo : cCurValue.getFuncCurrencyList()) {
                                System.out.println(field.getName() + ": " + curInfo.getValue() + " " + curInfo.getCurrencyCode());
                            }
                        }
                    } else if (field instanceof DateTimeField) {
                        Timestamp callDateTimeTS = (Timestamp) val.getValue();
                        if (callDateTimeTS != null) {
                            System.out.println(field.getName() + ": " + callDateTimeTS.toDate().toString());
                        }
                    } else if (field instanceof DiaryField) {
                        for (DiaryItem dlv : (DiaryListValue)val.getValue()) {
                            System.out.println(field.getName() + ": " + "Created by " + dlv.getUser() + " @ " + dlv.getTimestamp().toDate().toString() + " " + dlv.getText());
                        }
                    } else if (field instanceof AttachmentField) {
                        AttachmentValue aVal = (AttachmentValue) val.getValue();
                        if (aVal != null) {
                            String aName;
                            String[] aDetails = aVal.getValueFileName().split("\\.(?=[^\\.]+$)");
                            if (aDetails.length == 2) {
                                aName = aDetails[0] + "." + aDetails[1];
                            } else {
                                aName = aDetails[0];
                            }
                            int lastPos = aName.lastIndexOf('\\');
                            String aNameShort = (lastPos < 0) ? aName : aName.substring(lastPos + 1);
                            FileOutputStream fos = new FileOutputStream(aNameShort);
                            byte[] attach = ctx.getEntryBlob(schemaName, entryID, fieldID);
                            fos.write(attach);
                            fos.close();
                        }
                    } else {
                        System.out.println(field.getName() + ": " + val.toString());
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    Understanding Limitations & Best Practices for Data Extraction

     

    Everything we have seen above is meant to serve as a baseline for understanding of how to interact with key methods and data types provided in order to properly output information from the BMC API.  While good in theory, the reality is that such simple encapsulated methods are limited in their usefulness .  Here we will build upon what we have learnt above to look at building more complex and efficient methods for data extraction by leveraging different design patterns and will cover the considerations for the various options.

     

    getListEntryObject method

    To date we have been either using getEntry() alone for single entry returns or getListEntry() followed by iterative getEntry() calls to return multiple entries.  While both methods are correct and will work well in most situations, there is an alternative for the getListEntry() + multiple getEntry() design pattern.  The alternative is known as getListEntryObject().

     

    The getListEntryObject() method differs from the getListEntry() by actually returning a List<Entry> object instead of a List<EntryListInfo> object.  What this means is that if you need to return say 1,000 entry objects, you can do it in a single API call/transaction rather then having to do 1,001 API calls/ individual transactions by first getting a list of EntryListInfo and then subsequently calling getEntry for each item in the list.  While this seems like it would make the most sense to just use this getListEntryObject() method all the time, it has it’s own limitations/considerations that must be worked around.

     

    One of the key considerations for getListEntryObject() method is that you simply can’t tell it to return all values within the Entry object, you must include an int[] of all the field IDs to return (purpose being you should only ask it for what you need to minimize how much data needs to be pulled from the server).

     

    Another gotcha is that you must not include the Status History field (FieldID 15).  As you know Status History is stored in H tables in the database as opposed to the T tables which getListEntryObject() method executes against.

     

    Let’s look at what a getListEntryObjects method looks like in it’s simplest form:

     

    private static void getAPITutorialEntryObjects() {
        String schemaName = "API:TUTORIAL";
        try {
            List<Entry> entries = ctx.getListEntryObjects(schemaName, null, 0, 0, null, null, false, null);
            for (Entry entry : entries) {
                System.out.println(entry);
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
     

     

    By default, if you do not provide an int[] of field IDs to return from the getListEntryObjects method, it will only return a List of Entry objects that contain Field ID 1 and Field ID 8. This will be of limited usefulness so let’s see what happens when we attempt to build an int[] that contains all field IDs, including the status history.  We will need to build a utility method to convert a List<Integer> into an int[] so that we can use it in our getListEntryObject method.  The List<Integer> is the only format we can get from the getListField() method.

     

    private static void getAPITutorialEntryObjects() {
        String schemaName = "API:TUTORIAL";
        try {
            List<Integer> fieldIDList = ctx.getListField(schemaName, Constants.AR_FIELD_TYPE_DATA, 0);
            List<Entry> entries = ctx.getListEntryObjects(schemaName, null, 0, 0, null, convertIntegers(fieldIDList), false, null);                              
            for (Entry entry : entries) {
                System.out.println(entry);
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
    
    
    private static int[] convertIntegers(List<Integer> integers) {
        if (integers != null) {
            int[] ret = new int[integers.size()];
            Iterator<Integer> iterator = integers.iterator();
            for (int i = 0; i < ret.length; i++) {
                ret[i] = iterator.next();
            }
            return ret;
        }
        return null;
    }
     

     

    If we attempt to run our getAPITutorialEntryObjects() method, we will get an error that looks like this (on MS SQL, message will be different on Oracle):

    ERROR (552): The SQL database operation failed.; Invalid column name 'C15'. (SQL Server 207)

     

    The reason for this as discussed is that we cannot return an Entry object that contains data for the StatusHistory field (Field ID 15) directly.  What we can do is simply remove the Status History field from our array to get past this by adding the following before our getListEntryObjects() call.  Because we want to remove the Integer 15 from our List<Integer> object, we need to cast our value to Integer first so that the method doesn't think we mean the 15th index in the list.

     

    fieldIDList.remove((Integer) 15);

     

    If you can operate without requiring the Status History field, getListEntryObjects() will perform much faster and more efficiently then the pattern of getListEntry() + iterative getEntry() calls due to the single API call/transaction required.  We can see the difference in performance on these sample methods which both simply will result in List<Entry> being populated so that we would have a dataset to work on (this is an arbitrary example simply to highly the massive difference between these two methods).

     

    private static void getAPITutorialEntryList() {
        String schemaName = "API:TUTORIAL";
        List<Entry> entryList = new ArrayList<Entry>();
        try {
            List<EntryListInfo> entries = ctx.getListEntry(schemaName, null, 0, 0, null, null, false, null);
            for (EntryListInfo entryInfo : entries) {
                entryList.add(ctx.getEntry(schemaName, entryInfo.getEntryID(), null));
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
    
    
    private static void getAPITutorialEntryObjectsList() {
        String schemaName = "API:TUTORIAL";
        try {
            List<Integer> fieldIDList = ctx.getListField(schemaName, Constants.AR_FIELD_TYPE_DATA, 0);
            fieldIDList.remove((Integer) 15);
            List<Entry> entries = ctx.getListEntryObjects(schemaName, null, 0, 0, null, convertIntegers(fieldIDList), false, null);
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
    
    
    private static int[] convertIntegers(List<Integer> integers) {
        if (integers != null) {
            int[] ret = new int[integers.size()];
            Iterator<Integer> iterator = integers.iterator();
            for (int i = 0; i < ret.length; i++) {
                ret[i] = iterator.next();
            }
            return ret;
        }
        return null;
    }
     

     

    If we populated our API:TUTORIAL form with 50,000 entries and then executed both these methods, which have the same end result (except for the missing status history from the getListEntryObjects() method) of constructing a populated List<Entry> object that includes our 50,000 entries we see the following on my development environment:

    • getAPITutorialEntryList - 1 Minute and 1 second
    • getAPITutorialEntryObjectList - 4 seconds

    As we can see, the getListEntryObjects() method is faster by orders of magnitude when compared to the pattern of getListEntry + N*getEntry().

    In summary, unless you have a reason not to (such as the requirement of the Status-History field), always use the getListEntryObject method when you want to work on a set of Entries and it’s data.  If you simply need a list of entry IDs though, getListEntry() will be faster as it only transports a small set of data which will include the entry ID. For reference purposes, the getListEntry() call is what is used to display the default results lists in the BMC client tools so it is optimized for speed when you don’t need a lot of raw data as it has limitations on being able to return formatted data for diary fields and long character fields.

    MaxGetList Server Configuration Setting 

    A common scenario (and best practice setup) is to limit the number of entries that can be returned from getListEntry API calls.  By default this is set to 2,000 entries however administrators can set this to any number or 0 for unlimited.  In production environments, limiting the number of items returned per API call can have a significant performance influence on the overall solution.

    While this is great to keep our users and workflow in check, sometimes we need to work around this in our programs to work on large datasets.  Let us take the scenario above where we simply want to create a List<Entry> object of our 50,000 records, but in this scenario, the administrators have left the 2,000 limit on MaxGetList.  If we simply execute our program, we will only see 2,000 entries in our List<Entry> object.

    The getListEntryObject method provides special handling capabilities for this scenario that will allow us to iteratively build our List while still respecting the limitations set forth by the administrator.  Here will will look at a simple technique that utilizes 3 of the parameters of the getListEntryObjects() method that we simply defaulted to 0’s and null previously:

     

    private static void getAPITutorialEntryObjectsWalkLimits() {
        String schemaName = "API:TUTORIAL";
        OutputInteger totalCount = new OutputInteger();
        try {
            List<Integer> fieldIDList = ctx.getListField(schemaName, Constants.AR_FIELD_TYPE_DATA, 0);
            fieldIDList.remove((Integer) 15);
            int[] fieldIDs = convertIntegers(fieldIDList);
    
    
            ServerInfoMap maxGetListConfiguration = ctx.getServerInfo(new int[]{Constants.AR_SERVER_INFO_MAX_ENTRIES});
            int maxGetList = maxGetListConfiguration.firstEntry().getValue().getIntValue();
    
    
            List<Entry> entries = ctx.getListEntryObjects(schemaName, null, 0, maxGetList, null, fieldIDs, false, totalCount);
            if (entries.size() < totalCount.intValue()) {
                for (int i = entries.size(); i <= totalCount.intValue(); i += maxGetList) {
                    entries.addAll(ctx.getListEntryObjects(schemaName, null, i, maxGetList, null, fieldIDs, false, null));
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
    
    
    private static int[] convertIntegers(List<Integer> integers) {
        if (integers != null) {
            int[] ret = new int[integers.size()];
            Iterator<Integer> iterator = integers.iterator();
            for (int i = 0; i < ret.length; i++) {
                ret[i] = iterator.next();
            }
            return ret;
        }
        return null;
    }
     

    Let’s break down the above.  What is primarily different between the getAPITutorialEntryObjectsList() method and the getAPITutorialEntryObjectsWalkLimits() method is that we need to lay a bit of ground work first to identify what the server configuration says the MaxGetList is set to, and then we can use this to iterate as many times as needed within that limit to populate our full list of 50,000 entries.

    Let’s first look at how we find out what the current setting for MaxGetList is:

     

    ServerInfoMap maxGetListConfiguration = ctx.getServerInfo(new int[]{Constants.AR_SERVER_INFO_MAX_ENTRIES});
    int maxGetList = maxGetListConfiguration.firstEntry().getValue().getIntValue();
     

    The above will create a ServerInfoMap object using the getServerInfo() method.  We specifically pass in an int[] of all the settings we wish to return into our object as the ServerInfoMap is an extension of TreeMap so we can have many settings contained.  For our example, we explicitly give it an int[] containing only the int value that represents the MaxGetList (aka Max Entries Returned by Query) which is 28 however we can use the friendly Constants.AR_SERVER_INFO_MAX_ENTRIES instead to make it more readable.

    Once we have our ServerInfoMap object, we need to extract the actual limit that has been set and store it as an int.  Since we only put one row in our ServerInfoMap object (which is a TreeMap), we can utilize the .firstEntry().getValue().getIntValue() builder to get our value.  In our case out integer will be set to 2000.

    Now that we have the limit, lets see how to use it.  First, we need to review the 3 new parameters from the getListEntryObjects() method we will be using:

    • int firstRetreive
    • int maxRetrieve – This is how many to records to retrieve
    • OutputInteger nMatches

    Our first pass when compared to our previous example is extended to return the total number of records so that we can evaluate if we have the full data set or not, if not we can keep calling our getListEntryObjects() method with successively larger firstRetrieve values based on the increment of MaxGetList until we reach the total number of records.

     

    List<Entry> entries = ctx.getListEntryObjects(schemaName, null, 0, maxGetList, null, fieldIDs, false, totalCount);
    if (entries.size() < totalCount.intValue()) {
        for (int i = entries.size(); i <= totalCount.intValue(); i += maxGetList) {
            entries.addAll(ctx.getListEntryObjects(schemaName, null, i, maxGetList, null, fieldIDs, false, null));
        }
    }
     

    We see above, after our first pass, we just check to see if the size of our List<Entry> object is less then our total count, if it is, then using a for loop, simply iterate and add to our list until we reach the end of our entries.

     

    This completes the main part of this Java API Tutorial that is related to data.  Future updates will add sections for workflow objects and code/form generation as well as hopefully a better template to make it easier to follow.

     

    I hope this will prove useful to somebody.