Agiloft Developer Guide 1
Agiloft Developer Guide 1
API access requires you to have an Enterprise or Web Service license. Although it is possible to start Web Services
in the Setup menu, you will receive an error message if you attempt to access the APIs without the correct license.
For more information on upgrading your license, see Licenses.
The SOAP/REST interface is most suited for online integration between systems, rather than processes
that involve bulk synchronization. There are more suitable ways of synchronizing many records with Agiloft.
These include:
This tab also offers a Download HTML option that generates HTML code you can use to embed a form with a
Submit button, allowing the selected user to create a record in the table. Simply select the appropriate user, choose
the layout to use to display the form, and click Download HTML.
API Security
© 2022 Agiloft Inc. 6
API Security
In Setup > System > Manage Web Services, there are some options to configure the security of SOAP and
REST Web Services. At the top of the page, you can Enable WS if they aren't activated, or Disable or Re-enable
WS if they are currently enabled.
Four global variables allow you to define a set of IP addresses for SOAP and REST blacklisting and whitelisting:
IP addresses should be comma separated, and address ranges should be indicated with a dash.
Example
10.168.6.102, 10.169.7.132-10.169.7.150
REST Authentication
The REST client application must log in using valid credentials for a knowledgebase. Agiloft verifies these
credentials and if valid creates an HTTP session that the client can use in subsequent calls.
Every REST call should contain the user’s credentials in the form login={login}&password={password}.
Access Permissions
Call permissions are based on the authorized user's permissions in the Agiloft KB, so it will return only the data that
the API user has access to. Please note that in addition to the user's group permissions, it is necessary to grant
REST access to the group via Setup > System > Manage Web Services > Groups allowed for REST. Any
operation that the user doesn't have access to will be rejected.
Encryption
SOAP and REST calls can be encrypted by system admins. For example, consider a call to read data from a
contract record: http://\{localhost}/ewws/EWRead?$kb=KBNAME&$table=contract
Omit the variables likely to change, such as the ID value, from the string.
3. The encrypted URL can be used in the same way as the standard URL, and parameters can be added to it in
the standard way.
Example
{encrypted URL}&$lang=en&id=450
Statefulness
Though stateless in appearance Agiloft Web Services are stateful by nature. A certain set of information can be
shared as a "session" between multiple calls and there is usually no good reason to re-create it on every call.
A typical integration scenario obeys the pattern of "login, do multiple calls, logout".
The statefulness is supported via a widely used pattern of obtaining a session token first via the EWLogin method
and explicitly passing it with each subsequent call performed.
Once the session is finished it is advisable to destroy the session with a call to EWLogout to ensure proper freeing
of server resources.
Failed Logins
The SOAP API handles a series of failed logins in the same fashion as the GUI, i.e. it will produce an error if the
account becomes blocked due to multiple unsuccessful login attempts.
If any changes are made to the knowledgebase which should also be reflected in the WSDL, re-enable Web
Services then refresh the knowledgebase.
For example
The EWSelectandRead operation is generic and enables you to retrieve information from the
knowledgebase.
Table types extend a common EWWSBaseUserObject type. The generic methods use this common ancestor for
their arguments and result types where relevant. When a generic method is invoked for a specific table, it accepts
the table name as a string parameter and returns the corresponding specific descendant type as the result. The
code in the client must perform the necessary typecasting, if required. The name of the table is not used, as it can
be deduced from the invoked method.
Once you have the WSDL file, you need to import it into your development environment to generate the necessary
objects for building client Web service applications. For detailed instructions on testing the WSDL, see: Test the
SOAP Interface.
Before you run WSDL2java, you must have Axis installed on your system and all of its component JAR files
must be referenced in your classpath.
-a pathToWsdlDoc
The -a switch generates code for all elements, referenced or not, which may be necessary depending on your
WSDL.
If you have JAR files in more than one location, list them with a semicolon separating the files. For example, if the
Axis JAR files are installed in C:\axis-1_3, and the WSDL is named MyKB.wsdl and is stored in C:\myKB:
This command will generate a set of folders and Java source code files in the same directory in which it was run.
After these files are compiled, they can be included in your Java programs for use in creating client applications.
For most Java development environments, you can use wizard-based tools for this process instead of the command
line.
Using your development environment, you can construct Web Service (WS) client applications that use the
standard WS protocols to:
Log in to the knowledgebase and receive the session token to be used for subsequent calls.
Perform SQL-based searches across the knowledgebase data.
Create, read, update, and delete data.
Trigger workflow transitions and business rules processing.
Service requests and responses - your client application prepares and submits a service request to the
server via the API, the agiloft.com Web Service processes the request and returns a response, and the client
application handles the response.
Synchronous - once the API call is invoked, your client application waits until it receives a response from the
service. Asynchronous calls are not supported.
Committed automatically - every operation that writes to an agiloft.com object is committed automatically.
This is analogous to the AUTOCOMMMIT setting in SQL. Each API call is done in one transaction.
Followed by a delay inserted after the operation has completed. The delay is set to one second by default
and is configurable via the global variable Web Services Delay (WSDelay).
An operation on a record may invoke rules and other functions, which need to be allowed enough time
and resources to complete.
Additionally, the delay is needed because client applications could mistakenly used web services,
resulting in a flood of requests.
Run-time access via the SOAP API is governed by the same access permissions as the GUI. Certain operations on
the tables and fields can be performed only if the combined permissions in the logged-in user's group list permit
such access.
For example, values for the fields that are not visible to a given user are not returned in the results of the query.
Another often reported issue is when one is not able to write a structure, retrieved in the very previous call, back to
the server simply because the user has the necessary read permissions, but not the write ones.
Please refer to the Groups section in your User Manual or Online Help for full details regarding permissions.
As such, the API respects table-level permissions configured for the given user group in the knowledgebase.
Further, the API fully respects the ownership concept and related table-level and field-level permissions
configured in the knowledgebase.
Ownership for a record is determined in runtime at the time of the call and if modified in the call itself, comes
into effect immediately upon the completion of the API call.
Ownership changes to one object instance do not normally automatically cascade to other object instances
unless specifically configured so via a Linked Field with changes propagation. For example, if ownership
changes for a given Account, ownership does not then automatically change for any Contract associated
with that Account - each ownership change must be made separately and explicitly by the client application.
The configuration of the workflow for a given knowledgebase may influence the ability to create or delete a
record in a certain state or to change the state of a record.
Business rules, especially validation ones, may affect the success of create and modification operations.
Success also depends on whether a particular change would compromise the referential integrity of your
knowledgebase. For example, the data used in Linked Fields sets is validated in create and update calls. In a
similar fashion the delete calls are handled in a way to ensure the integrity of the data is maintained and will
fail otherwise.
Certain features that affect the agiloft.com user interface are not accessible or implicitly enforced via the API.
For example:
If any such constraints are required, it is up to the logic in the client application to enforce them explicitly.
Error Handling
In the event of an error the API calls return SOAP fault messages with additional information.
There are currently six types of faults distinguished by the SOAP API:
EWIntegrityException
Signals that the knowledgebase setup has become incompatible with the client application logic/expectations.
EWOperationException
Signals that the operation may not be performed because of some dependencies between agiloft.com functions.
This message should be considered in the context of the operation. It may signal a permanent condition, for
example workflow forbidding transition from one state to another; or a temporary one, for example the record is
locked by another user from another GUI or API session.
EWPermissionException
Signals that the username used to trigger the API call lacks sufficient privileges to perform certain operations.
EWSessionException
EWUnexpectedException
Signals that an unknown, not handled and unexpected exception has happened on the server side. These
exceptions should be reported to the vendor for further investigation. The message contains a token that helps to
trace the root cause of the problem.
EWWrongDataException
Signals that data passed by client is wrong in the context of the operation.
Prerequisites
You must have the admin login details for your knowledgebase.
The knowledgebase must have an Enterprise license. See Licensing for more information.
Download and install SoapUI from here: https://www.soapui.org/downloads/soapui.html. The Open
Source edition works for the testing described here.
3. The Enable WS button is replaced by two new buttons, and the message now says SOAP Web Services are
on.
4. Make sure that only the admin group has access to the SOAP and REST services.
5. Click the link to the WSDL, which opens a new tab in the URL format: http://<SERVER>/ewws/<KBNAME>
/EWServiceAPIv2?wsdl
5. Open the Service Endpoints tab of the interface viewer, and check the details of the WSDL URL. It should be
formed as above. Select the endpoint and click Assign.
6. In the Assign Endpoints dialog, select All Requests from the drop-down list, and click OK.
7. Exit the interface viewer.
Follow the example test cases below to test some of the available requests. Many of the examples below use the
Support Cases table (WSCase) to test the requests.
EWLogin
1. Add the following values to the EWLogin request:
<dem:EWLogin>
<String_1>KBNAME</String_1>
<String_2>admin</String_2>
<String_3>admin_password</String_3>
<String_4>en</String_4>
</dem:EWLogin>
2. Click Run. This returns something similar to the following response, which includes a session string between
the <result> tags:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<demo:EWLoginResponse xmlns:demo="http://Demo.api.ws.enterprisewizard.
com">
<result>78658161687694</result>
</demo:EWLoginResponse>
</env:Body>
</env:Envelope>
a.
<ns1:EWPermissionException xmlns:ns1="http://api.ws.enterprisewizard.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<message>not allowed, please check logs</message>
</ns1:EWPermissionException>
5. Enter the correct admin username and password again and click Run.
a. Write down the session string, which will be used in later tests.
EWRead
1. Add the following values to the EWRead_WSCase request and click Run:
<dem:EWRead_WSCase>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<recordID>0</recordID>
</dem:EWRead_WSCase>
EWSelectFromTable
1. In the Support Cases table, create a new record and assign it to the system user 'Ralph Knowles'; or if the
user does not exist, assign them to another user and replace String_3 below with their name.
a. Add the following values to the EWSelectFromTable request and click Run:
<dem:EWSelectFromTable>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<String_3>assigned_person='Ralph Knowles'</String_3>
</dem:EWSelectFromTable>
b. The response returns the ID of the Support Case, as well as any other Support Case records
assigned to that user.
2.
© 2022 Agiloft Inc. 20
2. Modify the string_3 parameter with a SQL query, which returns the record with the highest ID from the table.
Note that the [DB_TABLE_NAME] can be found in the Table Name field of the General tab of the Table
wizard. Click Run.
a. The response returns the ID of the highest record number for the Support Case table.
EWSelectAndRead
1. Add the following values to the EWSelectAndRead_WSCase request and click Run:
<dem:EWSelectAndRead_WSCase>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<where>assigned_person='Ralph Knowles'</where>
</dem:EWSelectAndRead_WSCase>
a. The response returns the details of any records assigned to user Ralph Knowles.
2. Modify the <where> condition so that it finds only one record; for example:
<where>assigned_person='Ralph Knowles' and wfstate=1</where>, and click Run.
a. The response contains the full data of that record.
EWCreate
1. Open the EWCreate_WScase request.
2. Remove all of the field tags apart from problem_Description and summary, and add the parameter
values in this way, then click Run:
<dem:EWCreate_WSCase>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<WSCase>
<problem_Description>Test ticket for SOAP<
/problem_Description>
<summary>SOAP test create</summary>
</WSCase>
</dem:EWCreate_WSCase>
3. This request creates a very basic new record in the Support Cases table. The response gives the record ID
in this way: <result>361</result>.
4. In the KB, locate the new record to verify that it was created successfully.
<dem:EWCreateAndRead_WSCase>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<WSCase>
<problem_Description>Test ticket 2 for SOAP<
/problem_Description>
<summary>SOAP test create and read</summary>
</WSCase>
</dem:EWCreateAndRead_WSCase>
3. This request creates a basic new record in the Support Cases table. The response gives the full data for the
new record.
4. In the KB, locate the new record to verify that it was created successfully.
EWUpdate
1. Open the EWUpdate_WSCase request.
2. Remove all of the field tags apart from ID and Priority and add the parameter values in this way, then
click Run:
<dem:EWUpdate_WSCase>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<WSCase>
<id>RECORD_ID_FOR_UPDATE</id>
<priority>OPTION_CRITICAL</priority>
</WSCase>
</dem:EWUpdate_WSCase>
3. This request changes the priority of the Support Case record to Critical. The response gives the full data for
the updated record.
4. In the KB, locate the record and check that its priority was changed.
EWDelete
© 2022 Agiloft Inc. 22
EWDelete
1. Add the following values to the EWDelete request and click Run:
<dem:EWDelete>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<arrayOflong_3>
<value>RECORD_ID_FOR_DELETE</value>
</arrayOflong_3>
<DeleteRule_4>APPLY_DELETE_WHERE_POSSIBLE</DeleteRule_4>
<arrayOflong_5>
</arrayOflong_5>
</dem:EWDelete>
2. This request deletes the record number in the KB. The response shows no record data.
3. In the KB, search for the record and confirm that it was deleted.
4. Execute the same request with the identical settings and check that the response is similar to the following:
<message>[http-0.0.0.0-80-5][1504006477104] Operation cannot be done. Not all
records can be removed.</message>
EWGetChoiceLineID
1. Add the following values to the EWGetChoiceLineID request and click Run:
<dem:EWGetChoiceLineId>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<String_3>priority</String_3>
<String_4>Critical</String_4>
</dem:EWGetChoiceLineId>
2. This request returns the line number in the Priority choice list for the value Critical. The response is a
value like <result>6</result>.
3. Change the <String_4> value to a non-existent priority and click Run.
a. The response is similar to the following message: <message>[http-0.0.0.0-80-4]
[1504007797277] No choice line found for value Defcon 6</message>
4. Change the <String_3> value to some non-choice field like 'summary' and click Run.
a. The response is similar to the following: <message>[http-0.0.0.0-80-4][1504007877613]
unexpected exception: null</message>.
<dem:EWAttachFromSOAPAttachment>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<tableName>case</tableName>
<key>419</key>
<fieldName>downloadable_files</fieldName>
<fileName>testfile.txt</fileName>
</dem:EWAttachFromSOAPAttachment>
3. Click Attachments below the request window, then click + and select the filename.txt file from your
system.
4. Click Run. The request attaches the file to the record in the Support Case table. The response is similar to
<result>1</result>.
5. In the KB, open the record and check that the file has been attached to the Downloadable Files field.
6. Create a new file and execute the same request again with it. Check that the result has been incremented to
<result>2</result>.
a. In the KB, open the record and check that the new file was added to the Downloadable Files field.
<dem:EWRetrieveAttachedAsSOAPAttachment>
<sessionId>SESSIONID_FROM_LOGIN_RESPONSE</sessionId>
<tableName>case</tableName>
<key>419</key>
<fieldName>downloadable_files</fieldName>
<filePosition>1</filePosition>
</dem:EWRetrieveAttachedAsSOAPAttachment>
2. The request retrieves the attached file from the Downloadable Files field with a file position of 1. The
response doesn't show any data, but below the response window the Attachments button indicates that there
is 1 attachment.
3. In the request, change the <filePosition> vaue to an incorrect attachment number like 80, then click
Run.
a. The response is similar to the following: <message>[http-0.0.0.0-80-7][1504013330298]
Blob field id in table case does not contain 81 files, but less (2) for
key 419</message>
EWRemoveAttached
1. Add the following values to the EWRemoveAttached request, replacing the <long> value with the Support
Case record number from the example above, then click Run:
<dem:EWRemoveAttached>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<long_3>419</long_3>
<String_4>downloadable_files</String_4>
<int_5>1</int_5>
</dem:EWRemoveAttached>
2. The request removes the first attached file from the Downloadable Files field. The response is similar to
<result>1</result>, with the number in the <result> tag being the number of remaining attached files.
3. In the KB, open the record and check that the first file was removed from the Downloadable Files field.
EWSearchTable
1. Add the following values to the EWSearchTable request, then click Run:
<dem:EWSearchTable>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<arrayOfString_3>
<value>id</value>
<value>summary</value>
</arrayOfString_3>
<String_4>Open Cases</String_4>
</dem:EWSearchTable>
2. The request searches the Support Cases table for all records with a status of Open. The response shows the
field values for each of the open cases.
3. Change the search name in String_4 to a non-existent search and click Run. The response is similar to
<message>No search=Open Cas for table=WSCase</message>.
EWSearchTablePaginated
1. Add the following values to the EWSearchTablePaginated request, then click Run:
<dem:EWSearchTablePaginated>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<arrayOfString_3>
2. The request searches the Support Cases table for the first page number (int_6) of the first record (int_5). The
response shows the fields on page 0 of the first record in the table.
3. Modify the <int_6> value to 2 (record count), then click Run.
a. The response shows the fields from the first two records.
4. Modify the <int_5> value to 1 (page number), then click Run.
a. The response shows the first two records on page 1 of the table.
5. Modify the <int_value> to 777, then click Run.
a. The response shows no records.
6. Modify <int_5> to 0, and <int_6> to 1000, then click Run.
a. The response shows all records in the table.
EWSearchTableWithQuery
1. Add the following values to the EWSearchTableWithQuery request, then click Run:
<dem:EWSearchTableWithQuery>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<arrayOfString_3>
<value>id</value>
<value>summary</value>
<value>priority</value>
</arrayOfString_3>
<String_4>Open Cases</String_4>
<String_5>priority='Low'</String_5>
</dem:EWSearchTableWithQuery>
2. The request searches the Support Cases table for all open cases with a priority of Low. The response shows
the fields for all records in Open status with a Priority of Low, including the ID Summary and Priority fields.
3. Change the condition for <String_5> to something like <String_5>priority='Low'&&
summary~='How'</String_5>, then click Run.
a. The response should return no errors and there should be a record data where the Summary field
contains the word 'How'.
4.
© 2022 Agiloft Inc. 27
4. Change the search in <String_4> to a non-existent value, then click Run.
a. The response is something like <message>No search=Cases for table=WSCase</message>
EWSearchTableWithQueryPaginated
1. Add the following values to the EWSearchTableWIthQueryPaginated request, then click Run:
<dem:EWSearchTableWithQueryPaginated>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
<String_2>case</String_2>
<arrayOfString_3>
<value>id</value>
<value>summary</value>
</arrayOfString_3>
<String_4>Open Cases</String_4>
<String_5>priority='Low'</String_5>
<int_6>0</int_6>
<int_7>1</int_7>
</dem:EWSearchTableWithQueryPaginated>
2. The request searches the Support Cases for open cases with a priority of Low, in the first record (int_7) on
the first page (int_6) in the table. The response shows all fields from that record.
3. Modify the <int_7> value to 2, to find the first two records, then click Run.
a. The response shows all the fields in the first two records on the first page of the table that match the
search query.
4. Modify the <int_6> value to 777 to find the first two records on the 777'th page of the table, then click Run.
a. The response shows no records.
5. Set the <int_6> value to 0, and <int_7> to 1000, then click Run.
a. The response shows the fields for all records in the table.
EWLogout
1. Add the following values to the EWLogout request, then click Run:
<dem:EWLogout>
<String_1>SESSIONID_FROM_LOGIN_RESPONSE</String_1>
</dem:EWLogout>
2.
© 2022 Agiloft Inc. 28
2. The request logs out the session. The response doesn't have any errors.
3. Execute any of the previous requests with the same values. The response is similar to <message>[http-
0.0.0.0-80-7][1504083695004] not logged in or this session has timed out<
/message>
5.
© 2022 Agiloft Inc. 30
5. Click OK.
6. In the Time To Live field that appears, enter the TTL value in the Agiloft Web Services wizard.
7. Exit from the settings wizard.
2. Click Run.
3. The response returns new timestamp data similar to the following:
<wsse:Security ...>
<wsu:Timestamp wsu:Id="timestamp">
<wsu:Created>2016-10-12T13:54:35.482Z</wsu:Created>
<wsu:Expires>2016-10-12T13:54:45.482Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
EWAttachFromSOAPAttachment
© 2022 Agiloft Inc. 32
EWAttachFromSOAPAttachment
Attaches a file into a field of the table record in your knowledgebase.
Syntax
int n = ew.EWAttachFromSOAPAttachment(String sessionId, String
tableName, long id, String fieldName, String fileName);
Usage
Use the EWAttachFromSOAPAttachment call to attach a file passed as a SOAP attachment into a file or image field
in the table using the specified file name.
The username that was used to obtain the specified session token must have sufficient access rights to
modify individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > (Edit Group) > Table > (Edit Table) > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to update the field content.
Please verify specific permissions via Setup > Access > Manage Groups > (Edit Group) > Table > (Edit
Table) > Field Permissions.
This call returns the current number of the attached files in the specified field.
One can use EWRead method to obtain an array of file names for the attached fields in the specified field.
Only one file per call is attached.
The file name does NOT have to be unique across multiple calls against the same record and field.
In general, you use EWAttachFromSOAPAttachment when you know in advance the identifiers of the
records to retrieve. The client application may use the likes of EWSelectFromTable call to obtain record
identifiers beforehand or to take the identifiers from the id field of the data structures and linking classes.
1. Determine the id of the record you want to update. You may want to use the EWSelectFromTable or
EWSearchTable calls to get the identifiers of the records based on some search condition or get the identifier
from a previous EWCreate call, or get the id of a linked record from the linking class after performing
EWRead or EWUpdate.
2. Wrap your file on the client-side in the matter specific to your client-side SOAP environment.
3. Call EWAttachFromSOAPAttachment.
4. Process the results.
Example Task
In MyKB knowledgebase as user A, attach from the client-side filesystem file B.txt located in the current directory to
the field Additional Files of case #456 and return the total number of files currently attached.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Prepare the file for transmission.
3. Invoke attach call.
4. Get total number of files currently attached.
5. Logout.
Arguments
Note: The file to be attached is passed as a SOAP attachment.
tableName String The name of the table where the record is.
fileName String The name of the file to be used in File or Image field.
Response
The current number of files attached in the specified field.
Faults
EWSessionException - client not logged in or session has expired; client should re-login.
EWPermissionException - user used to create the session lacks sufficient privileges to read the record.
EWWrongDataException - client has supplied wrong data, for instance ID cannot be found.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWCreate
© 2022 Agiloft Inc. 36
EWCreate
Adds a record to your knowledgebase data.
Syntax
long id = ew.EWCreate(String sessionId, String table,
EWWSBaseUserObject object)
or for the table-specific call:
long id = ew.EWCreate_WSCase(String sessionId, WSCase object)
Usage
Use EWCreate to add records to the tables, such as Support Case or People, in your knowledgebase.
The username that was used to obtain the specified session token must have sufficient access rights to
create individual records within the specified table. Please verify specific permissions by navigating to Setup
> Access > Manage Groups, editing the relevant group, clicking the Table tab, editing the relevant table
and clicking the Permissions tab.
Agiloft allows specifying fine-grained access permissions on the table field level. The username that was
used to obtain the specified session token must have sufficient access rights to set every field being
specified in the data object supplied in the call when creating the record. Consider this if you are using record
data obtained from or just used in another call like EWRead or EWUpdate - read and update permissions of
a particular user may not match the created ones. Please verify specific permissions by navigating to Setup
> Access > Manage Groups, editing the relevant group, clicking the Table tab, editing the relevant table
and clicking Field Permissions.
Linked Fields
Fields that are imported into the table from another table as a part of a Linked Fields set have to be filled via special
linking classes.
To forge a link the client has to create one or more instances of the linking class and select one of 3 options:
1. Specify the identifier previously obtained through the call to EWSelectFromTable, EWCreate or EWUpdate in
the "id" field of the linking class. Please note: some environments like .NET require a special property set for
simple-type fields to properly handle empty values - .<name>Specified = true for .NET.
2. Supply example data in the fields of the linking class that will be used to run Query-by-Example.
If any of the query mechanisms are used to identify the related records one has to take into account if the
relationship allows multiple records to be linked. If the query returns multiple results and the relationship doesn't
allow multiple records to be linked an EWOperationException is thrown.
Choice Fields
The values for choice columns should be supplied as instance(s) of the enumerated types described in the WSDL.
1. Instantiate the concrete subclass of EWWSBaseUserObject such as WSCase or WSContact. Populate its
fields with the data that you want to add.
2. Call create, passing in the table name and the data map.
3. Process the results.
Example Task
In MyKB knowledgebase as user A, create a case describing a lost present, with High priority, linked to contact
#125, owned by a Support Person and assigned to a Support Team.
1. Login to MyKB with "A" and "password", English as the locale language.
2. Create a new data structure, fill in the fields.
3. Specify a customer link to the Contacts record 124.
4. Specify the assignee Support Team from the Team table.
5. Specify an ownership link to the Contact record identified by the full name "Support Person".
6.
© 2022 Agiloft Inc. 39
6. Create the record.
7. Logout.
You can generate sample Web Services codes for any table by selecting Setup > Tables > [Select Table to Edit]
> API > Download Sample.
Arguments
Name Type Description
tableName String The name of the table where the record is to be created
- only for generic methods.
Response
The identifier of the record created.
Faults
EWSessionException - client not logged in or session has expired; client should re-login.
EWPermissionException - username used to create the session lacks sufficient privileges to perform record
modification.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWCreateAndRead
© 2022 Agiloft Inc. 41
EWCreateAndRead
Adds a record to your knowledgebase data and reads the result.
Syntax
EWWSBaseUserObject o = ew.EWCreateAndRead(String sessionId, String table,
EWWSBaseUserObject object)
or for the table-specific call:
WSCase case = ew.EWCreateAndRead_WSCase(String sessionId, WSCase object)
Usage
Use EWCreateAndRead to add records to the tables, such as Support Case or People, in your knowledgebase and
read the result in one call, potentially retrieving values for the fields set by business rules and other back-end logic.
The EWCreateAndRead call is analogous to an INSERT statement in SQL immediately followed by SELECT.
The username that was used to obtain the specified session token must have sufficient access rights to
create individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > [Edit Group] > Table > [Select Table] > Permissions.
Agiloft allows specifying fine-grained access permissions on the table field level. The username that was
used to obtain the specified session token must have sufficient access rights to set every field being
specified in the data object supplied in the call when creating the record. Consider this if you are using a
record data obtained from or just used in another call like EWRead or EWUpdate - read and update
permissions of a particular user may not match the created ones. Please verify specific permissions via
Setup > Access > Manage Groups [Edit Group] > Table > [Select Table] > Field Permissions.
The identifier of the new record is calculated automatically unless the field-level permissions allow the client
to supply the pre-calculated one and the client supplied one.
Linked Fields
Fields that are imported into the table from another table as a part of a Linked Fields set have to be filled via special
linking classes.
In WSDL a Linked Fields set takes form of DAO_Dao3_Link<N> field in the complex data structure that
corresponds to the target table, where N is a sequential number assigned automatically at the time of the set
creation, e.g. WSCase.DAO_Dao3_Link3.
As a value such fields can take one or more WS<Table1><Table2>_Dao3_Link<N> data structures - linking
classes, where Table1 is the target table and Table2 is the donor table of the Linked Fields relationship, e.g.
WSCaseTeams_Dao3_Link3.
Unfortunately, at this moment one has to rely on investigating the actual sets of fields inside the classes to trace the
fields, visible in the Field wizard in the GUI, back to the main object property.
Non-source values for Linked Field sets that allow them are present directly in the table itself additionally to those in
the Linking Classes, if the link was in fact forged.
To forge a link the client has to create one or more instances of the linking class and select one of 3 options:
1.
© 2022 Agiloft Inc. 43
1. Specify the identifier previously obtained through the call to EWSelectFromTable, EWCreate or EWUpdate in
the "id" field of the linking class. Please note: some environments like .NET require a special property set for
simple-type fields to properly handle empty values - .<name>Specified = true for .NET.
2. Supply example data in the fields of the linking class that will be used to run a Query-by-Example.
3. Supply the SQL statement similar in form to the one used for EWSelectFromTable in the special field
"searchSQL". Such SQL is evaluated prior to the creation of the record to identify the actual value for the
linked record identifier(s). This evaluation is done in exactly the same manner as when selecting from the
table. Normally this method would be used if the query is indirect, such as "where full_name like 'John%'.
If any of the query mechanisms are used to identify the related records, one has to take into account if the
relationship allows multiple records to be linked. If the query returns multiple results and the relationship doesn't
allow multiple records to be linked an EWOperationException is thrown.
Choice Fields
The values for choice columns should be supplied as instance(s) of the enumerated types described in the WSDL.
One has to perform the reverse transformation to get to the text value.
1. Instantiate the concrete subclass of EWWSBaseUserObject such as WSCase or WSContact. Populate its
fields with the data that you want to add.
2. Call EWCreateAndRead, passing in the table name and the data map or the table-specific variant with the
data map only.
3. Process the results.
Example Task
In MyKB knowledgebase as user A create a case describing a lost present, with High priority, linked to contact
#125, owned by a Support Person and assigned to the Support Team. Return the value from "date_created" field.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Create a new data structure, fill in the fields.
3. Specify a customer link to the Contacts record 124.
4. Specify the assignee Support Team from the Team table.
5. Specify an ownership link to the Contact record identified by the full name "Support Person".
6. Create the record.
7. Get date_created value from the returned object.
8. Logout.
You can generate sample a Web Services code for any table by selecting Setup > Tables > [Edit Table] > API >
Download Sample.
Arguments
Name Type Description
tableName String The name of the table where the record is to be created
(only for generic methods).
Response
The record data as a descendant of EWWSBaseUserObject - a complex structure described in WSDL.
EWPermissionException - username used to create the session lacks sufficient privileges to perform the record
modification.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWDelete
© 2022 Agiloft Inc. 47
EWDelete
Deletes one or more individual records from your knowledgebase data.
Syntax
ew.EWDelete(String sessionId, String tableName, long[] keys,
DeleteRule deleteRule, long[] replacementKeys);
Usage
Use EWDelete to delete one or more existing records from any given table.
The operation accepts a list of identifiers for the records to be deleted in one transaction. The transaction will either
succeed if each individual record has been successfully deleted or a rollback is performed otherwise.
The username that was used to obtain the specified session token must have sufficient access rights to
delete individual records within the specified table.
Agiloft allows one to establish relationships between the records in different tables and ensures the data
integrity once the links are forged.
In the presence of such relationships when the delete operation is performed via GUI, the user who triggers the
operation would be prompted via a series of dialogs to resolve the conflicts in each case.
When the same operation is triggered programmatically it is necessary to specify the policy to be applied for conflict
resolution beforehand via a deleteRule argument.
This is achieved via a deleteRule parameter that can take one of the four values:
Normally the decision which policy to use is taken at the "design" time and depends on the structure of the
knowledgebase.
Notes
If the specific strategy fails the error message returned will suggest alternatives to be used. One may use
REST interface to find the best strategy.
If APPLY_DELETE_WHERE_POSSIBLE or DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK
strategies are used and the configuration of the knowledgebase allows it, a special "Fast Delete" algorithm is
used, the same as via the GUI.
Certain objects cannot be deleted via the API.
1. Determine the id of each record that you want to delete. For example, you might call EWSelectFromTable to
retrieve the identifiers for the set of records that you want to delete based on a specific criteria.
2. Construct the keys[] array and populate it with the identifiers of each record that you want to delete.
3. If the REPLACE_WITH_ANOTHER delete policy is used construct the substitute replacementKeys[] array.
4. Call EWDelete, passing in the table name, the keys[] array, the policy identifier and the substitute keys array,
if necessary.
5. Process the results.
1. Login to MyKB with "A" and "password", English as the locale language.
2. Construct the keys array.
3. Call EWDelete with for case table with these keys and APPLY_DELETE_WHERE_POSSIBLE as delete
policy.
4. Logout.
tableName String The name of the table that contains records to be deleted.
keys long[] The array of one or more identifiers of the records to be deleted.
deleteRule DeleteRule The policy to be used to resolve the conflicts that may arise if the records
being deleted are referenced elsewhere.
May be one of the following:
1. ERROR_IF_DEPENDANTS
2. APPLY_DELETE_WHERE_POSSIBLE
3. APPLY_UNLINK
4. REPLACE_WITH_ANOTHER
Response
The call does not return any value. A transaction is either completed as a whole or a rollback is performed.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks sufficient privileges to perform a record deletion.
EWWrongDataException - supplied data is wrong; caused by either the record to delete cannot be found – cause,
array index, and key of the problematic record returned as parameters – or replacement failed – cause. array index,
key of the problematic record and key of the replacement one as parameters.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWGetChoiceLineId
© 2022 Agiloft Inc. 52
EWGetChoiceLineId
Obtains the internal identifier that corresponds to a choice value for use in SQL-based expressions.
Syntax
long id = ew.EWGetChoiceLineId(String sessionId, String
tableName,
String fieldName, String value);
Usage
Use EWGetChoiceLineId to obtain the internal identifier that corresponds to the choice text value for further use in
SQL-based expressions such as the where parameter of EWSelectFromTable or the value of searchSQL property
when forging links to other tables via Linked Field sets.
SQL-based expressions are evaluated on the database level where choice values are stored as identifiers. These
identifiers change if the knowledgebase is copied or re-imported, or the values in the choice list are re-created. It is
therefore advisable to use the EWGetChoiceLineId call to obtain the id value at runtime immediately before the SQL
call.
The value parameter is intentionally specified as String rather than an enumerated WSDL type. This is to
give client applications more flexibility.
If the client needs to lookup the identifier of the choice value enumerated in WSDL, the client should convert the
enumerated value into String. Since enumerated WSDL types that correspond to the choice values are based on
Strings, this should always be possible although specific details depend on the client Web Services environment
used.
When converting one of the WSChoice_ values back to String, please note that the original text values have
undergone the following transformations:
1.
© 2022 Agiloft Inc. 53
1. Spaces replaced by "_"
2. Dashes replaced by "MINUS"
3. Pluses replaced by "PLUS"
4. Prefixed with "OPTION_"
5. Converted to upper case
You need to perform the reverse transformation to get to the text value.
If the factors that trigger the change of the internal choice identifiers are somehow detected on the client,
then the values returned by this call can be safely cached on a per KB basis between multiple calls. As
described above, the internal choice field identifiers can be changed when a knowledgebase is copied or re-
imported, or if the values in the choice list are re-created.
Example Task
In MyKB knowledgebase as user A, find all cases with High priority.
1. Login to MyKB with "A" and "password", English as the locale language.
2. Obtain the identifier for the choice value High for the field Priority in the table Case.
3. Run the SQL query to obtain the identifiers.
4. Logout.
Arguments
Name Type Description
tableName String The name of the table where the choice field is located.
Response
The identifier that corresponds to the choice text value.
EWWrongDataException - the specified field does not exist in the table or the choice value passed cannot be
found.
EWIntegrityException - the specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWLogin
© 2022 Agiloft Inc. 56
EWLogin
Syntax
String sessionId = ew.EWLogin(String KB, String user, String
password, String language);
Usage
Use the EWLogin call to log in to Agiloft and start a client session. A client application must log in and obtain the
session token (sessionId) before making any other API calls.
When the client application invokes the EWLogin call, it passes the knowledgebase name - not the label - the
username and the password as user credentials. The language code is used in localization of error messages.
Specify "en" for English if not sure.
After logging in the client has to pass the obtained session token with every subsequent call as a parameter.
Example Task
Log into MyKB knowledgebase as user A with password "password" and English as the local language.
You can generate a sample Web Services code for any table by selecting Setup > Tables > [Select Table to Edit]
> API > Download Sample.
Arguments
Name Type Description
user String The username to be used to create the session defines access privileges for all further
calls within this session.
Response
The session token to be used in all subsequent calls.
Faults
EWPermissionException - user lacks the sufficient privileges to log in.
EWLogout
© 2022 Agiloft Inc. 59
EWLogout
Syntax
void ew.EWLogout(String sessionId);
Usage
Use EWLogout call to explicitly indicate the end of the session to facilitate freeing up of the server resources.
Generally, client applications are not required to logout. Sessions will expire automatically after a predetermined
length of inactivity, which can be configured via the global variable "disconnect_timeout". However, the explicit
logout ensures that the server resources are freed up faster which aids scalability.
It is advisable to enclose the EWLogout call in the Java finally block, or equivalent in other languages.
You can generate a sample Web Services code for any table by selecting Setup > Tables > [Select Table to Edit]
> API > Download Sample.
Faults
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWRead
© 2022 Agiloft Inc. 61
EWRead
Retrieves the record data from your knowledgebase.
Syntax
EWWSBaseUserObject o = ew.EWRead(String sessionId, String
tableName, long id);
or for the table-specific call:
WSCase wsCase = ew.EWRead_WSCase(String sessionId, long id);
Usage
Use the EWRead call to retrieve individual records from the table. The client application passes the tableName and
an identifier of the record to be read in the generic call; and just the record identifier in the table-specific call.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions by navigating to Setup >
Access > Manage Groups, editing the relevant group, clicking the Table tab, editing the relevant table and
clicking Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via by navigating to Setup > Access > Manage Groups, editing the relevant
group, clicking the Table tab, editing the relevant table and clicking Field Permissions..
This call does not return records that have been deleted.
This call never returns null when the record is not found.
Linked Fields
Values for the fields that are imported into the target table from the donor table as a part of a Linked Fields set are
available via special linking classes.
In WSDL a Linked Fields set takes form of a DAO_Dao3_Link<N> field in the complex data structure that
corresponds to the target table, where N is a sequential number assigned automatically at the time of the set
creation, such as WSCase.DAO_Dao3_Link3.
As a value, such fields can take one or more WS<Table1><Table2>_Dao3_Link<N> data structures; linking
classes, where Table1 is the target table and Table2 is the donor table of the Linked Fields relationship, such as\
WSCaseTeams_Dao3_Link3.
Unfortunately, at this moment one has to rely on investigating the actual sets of fields inside the classes to trace the
fields, visible in the Field Wizard in the GUI, back to the main object property.
Non-source values for Linked Field sets that allow them are present directly in the table itself additionally to those in
the Linking Classes, if the link was in fact forged.
Choice Fields
The values for choice columns are returned as instance(s) of the enumerated types described in the WSDL.
One has to perform the reverse transformation to get to the text value.
1. Determine the id of the record you want to update. You may want to use the EWSelectFromTable call to get
the identifiers of the records based on some search condition or get the identifier from a previous EWCreate
call, or get the id of a linked record from the linking class after performing another EWRead or EWUpdate.
2. Call EWRead.
3. Process the results.
Example Task
In MyKB knowledgebase as user A, read case #456 and return the value from the "date_created" field.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Read record 456 from table Case.
3. Get date_created value.
4. Logout.
You can generate a sample Web Services code for any table by selecting Setup > Tables > [Select Table to Edit]
> API > Download Sample.
Arguments
Name Type Description
tableName String The name of the table where the record is to be read (only for generic methods).
Response
The record data as a descendant of the EWWSBaseUserObject - a complex structure described in WSDL.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks the sufficient privileges to read the record.
EWOperationException - the operation has been blocked by an Agiloft function such as a table-level lock.
EWIntegrityException - the specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWRemoveAttached
© 2022 Agiloft Inc. 65
EWRemoveAttached
Removes the attached file specified by a position in the named field of the specified table record.
Syntax
Usage
Use the EWRemoveAttached call to remove an attached file from a File or Image field in a record in the table.
The username that was used to obtain the specified session token must have sufficient access rights to
modify individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > (Edit Group) > Table > (Edit Table) > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to update field content.
Please verify specific permissions via Setup > Access > Manage Groups > (Edit Group) > Table > (Edit
Table) > Field Permissions.
This call requires the position of the attached file in the specified field. Position numbering starts from 0.
One can use the EWRead method to obtain an array of file names for the attached field in the specified field.
The sequence of file names in the array will correspond to the positions of the files.
Only one file per call is removed.
In general, you use EWRemoveAttached when you know in advance the IDs of the records to retrieve. The
client application may use the likes of EWSelectFromTable call to obtain record identifiers beforehand or
take the identifiers from the id field of the data structures and linking classes.
1. Determine the id of the record you want to update. You may want to use the EWSelectFromTable or
EWSearchTable calls to get the identifier of the record based on some search condition or get the identifier
from a previous EWCreate call, or get the id of a linked record from the linking class after performing
EWRead or EWUpdate.
2. Determine the position of the file in the field.
3. Call EWRemoveAttached.
4. Process the results.
Example Task
In MyKB knowledgebase, as user A, remove all files named B.txt currently attached in the field Additional Files of
case #456 and return the total number of files currently attached.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Read the record to get the list of the files.
3. Find the position of the file.
4. Invoke the remove file call.
5. Get the total number of files currently attached.
6. Logout
Arguments
Name Type Description
tableName String The name of the table where the record is.
Response
The current number of files attached in the specified field.
Faults
EWSessionException - client not logged in or session has expired; client should re-login.
EWWrongDataException - client has supplied wrong data, for instance ID cannot be found.
EWOperationException - the operation has been blocked by and Agiloft function, for example a table-level lock.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has occurred; the admin user should report this for
investigation.
EWRetrieveAttachedAsSOAPAttachment 69
© 2022 Agiloft Inc.
EWRetrieveAttachedAsSOAPAttachment
Retrieves an attached file from the specified field of the table record in your knowledgebase.
Syntax
ew.EWRetrieveAttachedAsSOAPAttachment(String sessionId, String
tableName, long id, String fieldName, int position);
Usage
Use the EWRetrieveAttachedAsSOAPAttachment call to retrieve an attached file from a File or Image field in the
table record.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > (Edit Group) > Table > (Select Table) > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read the field content.
Please verify specific permissions via Setup > Access > Manage Groups > (Edit Group) > Table > (Edit
Table) > Field Permissions.
This call requires the position of the attached file in the specified field. Position numbering starts from zero.
One can use the EWRead method to obtain an array of file names for the attached fields in the specified
field. The sequence of file names in the array will correspond to the positions of the files.
Only one file per call is retrieved.
1. Determine the id of the record you want to update. You may want to use the EWSelectFromTable or
EWSearchTable calls to get the identifiers of the records based on some search condition; or get the
identifier from a previous EWCreate call; or get the id of a linked record from the linking class after
performing EWRead or EWUpdate.
2. Determine the position of the file in the field.
3. Call EWRetrieveAttachedAsSOAPAttachment.
4. Process the results.
Example Task
In MyKB knowledgebase as user A, retrieve the first file named B.txt currently attached in the field Additional Files
of case #456 in the current directory.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Read the record to get the list of the files.
3. Find the position of the file.
4. Invoke the retrieve file call.
5. Logout.
Arguments
Name Type Description
tableName String The name of the table where the record is.
fieldName String The name of the field to attach the files to.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks the sufficient privileges to read the record.
EWWrongDataException - client has supplied the wrong data, for instance ID cannot be found.
EWOperationException - the operation has been blocked by an Agiloft function, for example a table-level lock.
EWIntegrityException - the specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWSearchTable
© 2022 Agiloft Inc. 73
EWSearchTable
Executes a pre-configured named Saved Search against the specified table and returns and array of record data for
the records that match the search.
Syntax
EWWSBaseUserObject[] os = ew.EWSearchTable(String sessionId, String
tableName, String[] fieldNames, String searchName);
Usage
Use the EWSearchTable call to search for records in the specified table based on a Saved Search pre-configured in
the GUI.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > (Edit Group) > Table > (Select Table) > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via Setup > Access > Manage Groups > (Edit Group) > Table > (Edit Table) >
Field Permissions.
This call does not return records that have been deleted.
This method never returns null. In the case when no records are found an empty array is returned.
Special note on memory management: As WS integration implies pass-by-value semantics, the memory
allocated for the resulting array will be released once the data is sent to the client and server-side JVM's
garbage collector considers it eligible for discarding.
Linked Fields
Values for the fields that are imported into the target table from the donor table as a part of a Linked Fields set are
available via special linking classes.
In WSDL a Linked Fields set takes form of a DAO_Dao3_Link<N> field in the complex data structure that
corresponds to the target table, where N is a sequential number assigned automatically at the time of the set
creation (e.g. WSCase.DAO_Dao3_Link3).
As a value such fields can take one or more WS<Table1><Table2>_Dao3_Link<N> data structures - linking
classes, where Table1 is the target table and Table2 is the donor table of the Linked Fields relationship, for
example WSCaseTeams_Dao3_Link3.
Unfortunately, at this moment one has to rely on investigating the actual sets of fields inside the classes to trace the
fields, which are visible in the Field Wizard in the GUI, back to the main object property which is not visible in the
GUI.
Non-source values for Linked Field sets that allow them are present directly in the table itself and additionally to
those in the Linking Classes, if the link was in fact forged.
Choice Fields
The values for choice columns are returned as instances of the enumerated types described in the WSDL.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to the user used to login. Return summaries as a String
array.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Search for cases using My Assigned search.
3. Logout.
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed.
Response
An array of the records as descendants of EWWSBaseUserObject - a complex structure described in WSDL.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks the sufficient privileges to run the query.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has occurred; the admin user should report this for
investigation.
EWSearchTablePaginated
© 2022 Agiloft Inc. 78
EWSearchTablePaginated
Executes a pre-configured named Saved Search against the specified table and returns and array of record data for
the records that match the search. This operation variant allows iteration through results page by page, also called
pagination.
Syntax
EWWSBaseUserObject[] os = ew.EWSearchTablePaginated(String sessionId, String
tableName, String[] fieldNames, String searchName, int page, int
limit);
Usage
Use the EWSearchTablePaginated call to search for records in the specified table based on a Saved Search pre-
configured in the GUI. Specify page and limit parameters to retrieve data page by page.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > [Edit Group] > Table > [Select Table] > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via Setup > Access > Manage Groups > [Select Group to Edit] > Table > [
Select Table] > Field Permissions.
This call does not return records that have been deleted.
This method never returns null. In the case when no records are found an empty array is returned.
Linked Fields
Values for the fields that are imported into the target table from the donor table as a part of a Linked Fields set are
available via special linking classes.
As a value such fields can take one or more WS_Dao3_Link data structures - linking classes, where Table1 is the
target table and Table2 is the donor table of the Linked Fields relationship, for example
WSCaseTeams_Dao3_Link3.
Unfortunately, at this moment one has to rely on investigating the actual sets of fields inside the classes to trace the
fields, which are visible in the Field wizard in the GUI, back to the main object property, which is not visible in the
GUI.
Non-source values for Linked Field sets that allow them are present directly in the table itself and additionally to
those in the Linking Classes, if the link was in fact forged.
Choice Fields
The values for choice columns are returned as instances of the enumerated types described in the WSDL.
2.
© 2022 Agiloft Inc. 81
2. Perform the call using the name of the search as the "searchName" parameter.
3. Specify page and limit values.
4. Handle the results, specifically the situations where there are no elements, one element, or more than one
element in the returned array.
5. Iterate to the next page as required.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to the user used to login. Return the first 40 summaries
as two String arrays, up to 20 elements each.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Search for cases using My Assigned search.
3. Logout.
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed.
Response
An array of the records as descendants of EWWSBaseUserObject - a complex structure described in WSDL.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks the sufficient privileges to run the query.
EWOperationException - the operation has been blocked by an Agiloft function, for example a table-level lock.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWSearchTableWithQuery
© 2022 Agiloft Inc. 84
EWSearchTableWithQuery
Executes a preconfigured named Saved Search against the specified table and/or an ad-hoc query and returns and
array of record data for the records that match the criteria.
Syntax
EWWSBaseUserObject[] os = ew.EWSearchTableWithQuery(String sessionId, String
tableName, String[] fieldNames, String searchName, String query);
Usage
Use EWSearchTableWithQuery call to search for records in the specified table based on a Saved Search pre-
configured in the GUI and/or to filter the records with an ad-hoc query.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > (Edit Group) > Table > (Edit Table) > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via Setup > Access > Manage Groups > (Edit Group) > Table > (Edit Table) >
Field Permissions.
This call does not return records that have been deleted.
This method never returns null. In the case when no records are found an empty array is returned.
Special note on memory management: As WS integration implies pass-by-value semantics, the memory
allocated for the resulting array will be released once the data is sent to the client and the server-side JVM's
garbage collector considers it eligible for discarding.
If the client-side environment is the one with a garbage collector, the memory used by client-side array will
be cleared once the client-side garbage collector considers it eligible.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to the user used to login with low priority. Return
summaries as a String array.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Search for cases using My Assigned search, additionally filtering by low priority.
3. Logout
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed.
Response
An array of the records as descendants of EWWSBaseUserObject - a complex structure described in WSDL.
EWPermissionException - user used to create the session lacks sufficient privileges to run the query.
EWOperationException - the operation has been blocked by an Agiloft function, for example a table-level lock.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; the admin user should report this for
investigation.
Example Result
Priority=Low OK
'Priority'='Low' OK
'Bug Priority'=Low OK
Simple criteria
Simple criteria has the form of
<field name><operator><value>
Operator Definition
= equals
!= not equals
~= contains
> greater
< lesser
<< included by
The included/not included by operators (<<, !<<) expect a comma-separated list of values, without spaces, at the
right-hand side of the equation and checks if field value is included (or not-included) in this list. In other words,
Priority << High,Low is a short-hand for Priority=High || Priority=Low, where || is the OR operator, as described
below.
Logical criteria
Allows to combine other criterias using AND and OR operators. '&&' is AND, '||' is OR.
Operator precedence
Expression is evaluated from left to right, braces may be used for grouping. For example 'A && B || C' means '(A &&
B) || C'.
Time-based criteria
Allows to set relative date constraints. The form is <field name><operator><mode><value>, where operator is one
of =,!=,<,>,<=,>=, mode is either '-', which means 'old', '+', which means 'in the future' or '#', which means 'absolute'.
'value' is an integer followed by a single character:
h hour
w week
M month
y year
Examples:
Currently, more complex expressions like 'two years, one month and three hours' are not supported.
Advanced criteria
Advanced criteria has the form of <field name>:<from>-><to> and means 'field <field name> has changed from from
to to' Either from or to – but not both at the same time – may hold '?' meaning 'any value'.
This criteria searches through record history, and thus the history column must exist and must track changes to the
specified field. All simple and time-based criteria have implicit 'now' flag set, which means that they will match the
current record state, not the state when advanced criteria has been satisfied. In the other words, if we have a record
with the following modification history, with the bottom state being the most recent:
More examples
Status=Open && ('Assigned To' = john || 'Assigned To' = jane)
(Priority>High || Summary ~= Urgent) && State:Closed->Reopened
OS=Windows,Linux && 'Modification Date' < -1y
EWSearchTableWithQueryPaginated
© 2022 Agiloft Inc. 90
EWSearchTableWithQueryPaginated
eExecutes a preconfigured named Saved Search against the specified table and/or an ad hoc query and returns
and array of record data for the records that match the criteria. This operation variant allows pagination - iteration
through results page by page.
Syntax
EWWSBaseUserObject[] os = ew.EWSearchTableWithQueryPaginated(String sessionId,
String tableName, String[] fieldNames, String searchName, String query, int page,
int limit);
Usage
Use EWSearchTableWithQueryPaginated call to search for records in the specified table based on a Saved Search
pre-configured in the GUI and/or to filter the records with an ad-hoc query. Specify page and limit parameters to
retrieve data page by page.
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > [Edit Group] > Table > [Select Table] > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via Setup > Access > Manage Groups > [Select Group to Edit] > Table > [
Select Table] > Field Permissions.
This call does not return records that have been deleted.
This method never returns null. In the case when no records are found an empty array is returned.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to the user used to login with low priority. Return the
first 40 summaries as two String arrays, up to 20 elements each.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Search for cases using My Assigned search, additionally filtering by low priority.
3. Logout
You can generate sample Web Services code for any table by selecting Setup > Table > [Select Table to Edit] >
API > Download Sample.
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed.
Response
An array of the records as descendants of EWWSBaseUserObject - a complex structure described in WSDL.
EWPermissionException - user used to create the session lacks sufficient privileges to run the query.
EWOperationException - the operation has been blocked by an Agiloft function, e.g. table-level lock.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; the admin user should report this for
investigation.
Example Result
Priority=Low OK
'Priority'='Low' OK
'Bug Priority'=Low OK
Simple criteria
Simple criteria has the form of
<field name><operator><value>
Operator Definition
= equals
!= not equals
~= contains
> greater
< lesser
<< included by
The (not) included by operator expects a comma-separated list of values without spaces at the right-hand side and
checks if field value is included into this list. In other words Priority << High,Low Is a short-hand for Priority=High ||
Priority=Low, where || is OR, as described below.
Logical criteria
Allows to combine other criterias using AND and OR operators. '&&' is AND, '||' is OR.
Operator precedence
Expression is evaluated from left to right, braces may be used for grouping. For example 'A && B || C' means '(A &&
B) || C'.
Time-based criteria
Allows to set relative date constraints. The form is <field name><operator><mode><value>, where operator is one
of =,!=,<,>,<=,>=, mode is either '-', which means 'old', '+', which means 'in the future' or '#', which means 'absolute'.
'value' is an integer followed by a single character:
h hour
w week
M month
y year
Examples:
Currently, more complex expressions like 'two years, one month and three hours' are not supported.
Advanced criteria
Advanced criteria has the form of <field name>:<from>-><to> and means 'field field name has changed from from to
to' Either from or to but not both at the same time may hold '?' meaning 'any value'.
This criteria searches through record history, and thus the history column must exist and must track changes to the
specified field. All simple and time-based criterias have implicit 'now' flag set, which means that they will match the
current record state, not the state when advanced criteria has been satisfied. In the other words, if we have a record
with the following modification history, with the bottom state being the most recent:
More examples
Status=Open && ('Assigned To' = john || 'Assigned To' = jane)
(Priority>High || Summary ~= Urgent) && State:Closed->Reopened
OS=Windows,Linux && 'Modification Date' < -1y
EWSelectAndRead
© 2022 Agiloft Inc. 97
EWSelectAndRead
Executes a query against the specified table and returns an array of record data for the records that match the
specified criteria.
Syntax
EWWSBaseUserObject[] os = ew.EWSelectFromTable(String sessionId, String
tableName, String whereClause);
or for the table-specific call:
WSCase[] cases = ew.EWCreateAndRead_WSCase(String sessionId, String whereClause)
Usage
Use EWSelectFromTable call to search for records in the specified table.
The SQL query supplied should only include the "where" clause from a construct similar to:
The username that was used to obtain the specified session token must have sufficient access rights to read
individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > [Edit Group] > Table > [Select Table] > Permissions.
Agiloft allows specifying fine-grained access permissions on the field level. The username that was used to
obtain the specified session token must have sufficient access rights to be able to read field content. Please
verify specific permissions via Setup > Access > Manage Groups [Edit Group] > Table > [Select Table] >
Field Permissions.
This call does not return records that have been deleted.
Linked Fields
Values for the fields that are imported into the target table from the donor table as a part of a Linked Fields set are
available via special linking classes.
In WSDL a Linked Fields set takes form of DAO_Dao3_Link<N> field in the complex data structure that
corresponds to the target table, where N is a sequential number assigned automatically at the time of the set
creation, such as WSCase.DAO_Dao3_Link3.
As a value such fields can take one or more WS<Table1><Table2>_Dao3_Link<N> data structures - linking
classes, where Table1 is the target table and Table2 is the donor table of the Linked Fields relationship, such as
WSCaseTeams_Dao3_Link3.
Unfortunately, at this moment one has to rely on investigating the actual sets of fields inside the classes to trace the
fields, visible in the Field wizard in the GUI, back to the main object property - not visible in the GUI.
Non-source values for Linked Field sets that allow them are present directly in the table itself and additionally to
those in the Linking Classes, if the link was in fact forged.
One has to perform the reverse transformation to get to the text value.
Note: "Prepared" statements are not supported, all parameters have to be passed inside of the whereClause
string.
2. Perform the call.
3. Handle results, specifically the situations where there are no elements, one element, or more than one
element in the returned array.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to John Doe. Return summaries as a String array.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Search for cases assigned to John Doe.
3. Logout
You can generate sample Web Services code for any table by selecting Setup > Tables > [Edit Table] > API >
Download Sample.
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed - only for generic methods
Response
© 2022 Agiloft Inc. 101
Response
An array of the records as descendants of EWWSBaseUserObject - a complex structure described in WSDL.
Faults
EWSessionException - client not logged in or session has expired; client should re-login.
EWPermissionException - user used to create the session lacks sufficient privileges to run the query.
EWOperationException - the operation has been blocked by an Agiloft function, e.g. table-level lock.
EWIntegrityException - specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; the admin user should report this for
investigation.
EWSelectFromTable
© 2022 Agiloft Inc. 102
EWSelectFromTable
Executes a query against the specified table and returns record identifiers that match the specified criteria.
Syntax
long ids[] = ew.EWSelectFromTable(String sessionId, String
tableName, String whereClause);
Usage
Use the EWSelectFromTable call to search for records in the specified table.
The SQL query supplied should only include the "where" clause from a construct similar to:
The queries operate on the database level and should be written in terms of names visible in the Agiloft GUI
in the Table wizard in the dbname column. Also see EWGetChoiceLineId for special handling of choice
values.
This method never returns null. In the case where no records are found an empty array is returned.
Special note on memory management: As WS integration implies pass-by-value semantics, the memory
allocated for the resulting array will be released once the data is sent to the client and the server-side JVM's
garbage collector considers it eligible for discarding.
If the client-side environment is one with a garbage collector, the memory used by client-side array will be
cleared once the client-side garbage collector considers it eligible.
When the client environment uses explicit memory management, the client is responsible for freeing up the
used memory explicitly.
Example Task
In MyKB knowledgebase, as user A, find all cases assigned to John Doe.
1. Log in to MyKB with "A" and "password", English as the local language.
2. Search for cases assigned to John Doe.
3. Log out.
Arguments
Name Type Description
tableName String The name of the table where the query has to be performed.
Response
The identifiers of the records that match the specified criteria.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - user used to create the session lacks the sufficient privileges to run the query.
EWOperationException - the operation has been blocked by an Agiloft function, for example a table-level lock.
EWIntegrityException - the specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
EWUpdate
© 2022 Agiloft Inc. 105
EWUpdate
Updates a record in your Agiloft knowledgebase.
Syntax
EWWSBaseUserObject res = ew.EWUpdate(String sessionId, String
table,
EWWSBaseUserObject obj);
or for the table-specific call:
WSCase wsCase = ew.EWUpdate_WSCase(String sessionId, WSCase case);
Usage
Use EWUpdate to update records in a table, such as Case or Contact, in your knowledgebase. The client
application passes the tableName and the map with data to be applied to the record in the generic call and just the
map in the table-specific call.
The username that was used to obtain the specified session token must have sufficient access rights to
update individual records within the specified table. Please verify specific permissions via Setup > Access >
Manage Groups > [Edit Group] > Table > [Select Table] > Permissions.
Agiloft allows specifying fine-grained access permissions on the table field level. The username that was
used to obtain the specified session token must have sufficient access rights to set every field being
specified in the data object supplied in the call on update of the record. Consider this if you are using the
record data obtained from or just used in another call like EWRead or EWCreate - read and create
permissions of a particular user may not match the updated ones. Please verify specific permissions via
Setup > Access > Manage Groups > [Edit Group] > Table > [Select Table] > Field Permissions.
1. Determine the id of the record you want to update. You may want to use the EWSelectFromTable call to get
the identifiers of the records based on some search condition or get the identifier from a previous EWCreate
call, or get the id of a linked record from the linking class after performing an EWRead.
2.
© 2022 Agiloft Inc. 107
2. If you do not have it already from one of the previous calls, construct an instance of the complex type that
corresponds to the table you are trying to update. Fill in the identifier and those fields that need updating.
3. Call EWUpdate.
4. Process the results.
Example Task
In MyKB knowledgebase, as user A, update a given case, setting the field Escalate to Support Staff to Yes (in this
particular KB this triggers a round-robin assignment to the members of the Support Team). Return the name of the
employee who is assigned to the case as a result.
1. Login to MyKB with "A" and "password" and English as the local language.
2. Set the Escalate to Support Staff field to Yes.
3. Update the record.
4. Read the name of the assignee.
5. Logout.
You can generate a sample Web Services code for any table by selecting Setup > Tables > [Edit Table] > API >
Download Sample.
tableName String The name of the table where the record is to be updated - only for
generic methods.
Response
Updated record data as a descendant of EWWSBaseUserObject - a complex structures of the same type as the
object argument.
Faults
EWSessionException - client not logged in or the session has expired; client should re-login.
EWPermissionException - username used to create the session lacks the sufficient privileges to perform record
modification.
EWWrongDataException - data causes an index or constraint violation; key, name of problematic column and the
value causing the problem as parameters.
EWIntegrityException - the specified table cannot be found or its primary key cannot be identified.
EWUnexpectedException - an unexpected exception has happened; user should report this for investigation.
SoapUI
When using generic methods a specific descendant of the EWWSBaseUserObject structure needs to be passed.
This is achieved by adding the xsi:type specialization to the tags defining the structure in the method call
parameters.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tsc="http://KBname.api.ws.agiloft.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<tsc:EWCreate>
<String_1>d57d</String_1>
<String_2>case</String_2>
<EWWSBaseUserObject_3 xsi:type="tsc:WSCase">
<summary>Example 1</summary>
</EWWSBaseUserObject_3>
</tsc:EWCreate>
</soapenv:Body>
</soapenv:Envelope>
IIS Integration
© 2022 Agiloft Inc. 110
IIS Integration
If you are integrating Agiloft with Microsoft Internet Information Services (IIS), please modify the web.config file of
the IIS to include the <httpErrors existingResponse="PassThrough" /> tag:
Example
This will allow proper system exceptions to pass through the IIS API, rather than the default IIS errors. Explanation
on the other available values is given at http://stackoverflow.com/questions/31040671/what-does-existingresponse-
passthrough-mean-in-iis
REST Interface
© 2022 Agiloft Inc. 111
REST Interface
Agiloft supports REST-style invocations that correspond to CRUD operations: Create, Read, Update, Delete.
Additionally limited SQL select functionality is supported via Select, and saved search and ad hoc queries via
Search. Attachments can be managed via via the Attach, RemoveAttached, and RetrieveAttached calls. Login and
Logout functions are provided for session security.
This allows you to automate certain actions from external agents such as browser forms, JavaScript/AJAX.
URL Conventions
The following general conventions apply to how the URL is constructed:
For pure REST, where the /{id}part is omitted for Create (POST), but used for all others:
/ewws/REST/{kbName}/{table}[/{id}]?$login={login}&password={password}&lang={lang}
&...
/ewws/{operation}?$KB={kbName}&$table={table}&$login={login}&password={password}
&lang={lang}&...
The body (for POST and PUT) or the rest of the URL string should contain the parameter name/value pairs, as per
operation specification.
Example
Or
Notes
Both knowledgebase names and table names are case sensitive when using the REST interface.
To find the correct case for your table, go to Setup > Tables, select your table, click Edit, and look
for the Logical Table Name. This is the name and capitalization you should use when accessing that
particular table.
Return Values
Return values are returned in an encoded form suitable for the JavaScript eval() operation to be applied.
Extended characters (like ö) are returned in a UTF-encoded format. After this, the values can be accessed from
local variables. Fields with empty values are not returned.
To avoid interfering with variables that may already exist in the client script or document, all table column names in
the variables that result from the eval() call are prefixed with EWREST_. As such, what is returned is escaped
using JavaScript rules. The content type of the field is irrelevant for the escaping.
EWREST_company_name='Agiloft';
EWREST__1794_full_name=' agiloft.com Admin';
EWREST_website_url=' http://www.agiloft.com'
EWREST_date_updated=' 21 8 06 15:18:43 PM';
EWREST_id=' 21';
You may want to consider using a JSON decorator to receive a JSON formatted stream instead, since
JSON has more readily available parsers. Here is its syntax in a REST call:
https://<hostname>/ewws/EWRead/.
json?$KB=KB&$table=<table>&$login=admin&$password=<pwd>$lang=en&id=<id>
{"success":true,"message":"","result":{...,"company_name":"Agiloft","
_1794_full_name":"Agiloft System","id":21}}
Delays
Each call via the REST interface has a delay inserted after the operation has completed. The delay is set to one
second by default and is configurable via the global variable Web Services Delay (WSDelay).
An operation on a record may invoke rules and other functions, which need to be allowed enough time and
resources to complete.
Additionally, the delay is needed because client applications could mistakenly used web services, resulting in
a flood of requests.
Security
Use the Login and Logout operations to secure sessions initiated by REST.
Login: Use this operation to authenticate KB credentials and return a token, which can then be used by
following requests to avoid including login credentials in request URLs. Input the KB login and password as
parameters, and the operation returns a token, expiration time, and authentication scheme to the client. The
token can then be used in an Authorization request header, prefixed by the authentication scheme, instead
of including the login and password parameters in following requests. By default, the Bearer authentication
scheme is used, and the token expiration time is 15 minutes. You can adjust the expiration time by creating a
token_expires_in global variable and setting the number of minutes, up to 60.
Logout: Use this operation to terminate a session created by the Login operation. This terminates the
session associated with the token passed in the Authorization header.
_token":"…","
expiration_time_unit":"
minute","expires
_in":5,"
authentication_scheme":"
Bearer "}
Data Encoding
The following conventions are in place to encode aspects of a typical Agiloft knowledgebase:
Simple fields
© 2022 Agiloft Inc. 115
Simple fields
Simple fields can be filled directly by setting the value for them.
... &first_name=John&last_name=Doe&...
Choice fields
Choice fields are encoded directly with their text values as seen in the GUI.
...&country=USA&...
For ad hoc queries, in the Select call choice values should be addressed via the ID values obtained from
GetChoiceLineId.
Multi-choice fields
Multi-choice fields are encoded as multiple key/value pairs.
... &contactMethod=phone&contactMethod=email&...
Please refer to the following document to see the list of supported date-time formats: datetime.txt
...&company_name=Agiloft&...
To create a link based on the values of imported columns, you can use Query By Example, expressed with a colon
':' qualifier.
Example values have to be provided for one or more of the imported columns in the following way:
...&company_name=:Agiloft&...
Or
...&company_name=Company:Agiloft&...
where donor table name is required if the link type is "single field from multiple tables", but may be omitted in the
case of a single donor table.
If the value contains the colon ':' symbol or the question mark '?' symbols, they have to be escaped with backward-
slash "\" in the following way:
...&company_url=Company:http\://www.example.com&...
For more complex queries, possibly including sub-selects, aggregative functions and columns from the donor table
that are not imported into the target, you can use a SQL query, expressed with a question mark '?' qualifier. The
"where" clause follows the qualifier for the query that will be run against the donor table. The columns in the query
have to be referred with their database names rather then logical ones.
Example
To look for Employee records where the Company currency is EUR, and not in the linked set, use the
following search:
http://localhost:8080/ewws
/EWSearch?$login=admin&$password=qwerty&$lang=en&$table=contacts.
employees&$KB=Demo&query=_1576_company_name0=Company?currency~='EUR'
...&company_name=Company:Agiloft& company_name=Company:SaaSWizard&...
The name of the form field should match the name of the file or image field. Additionally a field fieldName$overwrite
can be specified with any value to instruct the REST interface to override the current data in the file or image field,
rather than add.
Application clients can use REST - Attach operation instead with PUT HTTP method.
Decorators
REST calls allow use of three decorators:
Asynchronous decorator
Can be applied to EWCreate, EWUpdate and EWDelete calls to do "fire-and-forget" type of call.
Should be used if the results normally returned by an operation are not important to the caller e.g. a
scenario when a lot of records have to be created in the backend.
Use
/ewws/async/EWCreate?...
or
/ewws/EWCreate/.async?...
Redirect decorator
Can be applied to all calls to have a HTTP redirect issued depending on the success of the operation.
Useful the scenarios of integration with web sites. Please note the page to which redirect is performed
will NOT receive any return data.
Use
/ewws/redirect/EWCreate?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
or
/ewws/EWCreate/.redirect?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
Use
/ewws/EWRead/.json?...
Please note that decorators can be chained. In the case of chaining they are applied from left to right.
Use
/ewws/redirect/EWCreate/.async?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
/ewws/async/EWCreate/.redirect?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
/ewws/redirect/async/EWCreate?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
/ewws/async/redirect/EWCreate?...&$exiturl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.
com&$errorurl=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttp%2Fwww.google.com%2F404
REST - Create
© 2022 Agiloft Inc. 119
REST - Create
The EWCreate (POST) operation...
Creates a record in the specified KB and table with values for the specified fields.
Accepts the URL with parameters as per general URL conventions and record data encoded as per Data
Encoding, both of which can be viewed in the REST Interface.
Supported Content-Type: application/x-www-form-urlencoded
Returns the identifier of the newly created record.
If this operation is used asynchronously, its status can be checked with EWAsyncStatus.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Create a record for
an employee named John Doe, that is assigned to the Service Manager group and Service Management
Team, with login 'jdoe' and password 'password'.
http://localhost:8080/ewws/EWCreate?$KB=Demo&$table=contacts.
employees&$login=admin&$password=qwerty&$lang=en&first_name=John&last_name=Doe&_login=jdo
A result similar to the following will be returned in the case of successful record creation:
EWREST_id='353';
Here is an example of a JavaScript-based client that invokes the REST interface via AJAX:
REST - Read
© 2022 Agiloft Inc. 121
REST - Read
The EWRead REST operation:
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". The updated
record can be seen in the Update example.
http://localhost:8080/ewws/EWRead?$KB=Demo&$table=Contacts.
Employees&$login=admin&$password=qwerty&$lang=en&id=358
EWREST_full_name='John Doe';
EWREST_first_name='John';
EWREST__1576_company_name0='IBM';
EWREST_f_group_0='Service Manager';
EWREST_id='358';
EWREST__106_sw_description='Service Management Team';
EWREST__login='jdoe';
EWREST_date_updated='Dec 27 2017 04:40:24';
EWREST_type='employees';
EWREST_date_created='Dec 27 2017 04:34:38';
EWREST_rep_email='[email protected]';
EWREST_default_approval_title='Document Approval';
EWREST_last_name='Doe';
Here is an example for a JavaScript-based client that invokes the REST interface via AJAX:
REST - Update
© 2022 Agiloft Inc. 123
REST - Update
The EWUpdate REST operation:
Updates the specified record, implementing the Update operation of the REST interface.
Accepts the URL with parameters as per general URL and record data conventions, both of which can be
viewed in the REST Interface Overview.
Supported Content-Type: application/x-www-form-urlencoded
Returns the encoded data of the updated record.
If this operation is used asynchronously, its status can be checked with EWAsyncStatus.
In place of the usual ID parameter, you may instead use $searchSQL to identify the record to update,
provided that the search returns exactly one record and no more. For example, if you have a unique ext_id
field in Agiloft, you can use this to update a single record that has ext_id='a0B2c345' using
&$searchSQL=ext_id='a0B2c345' in the URL.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Update the record
created in the REST Create example section by linking that employee to an opportunity record for
company Agiloft. This operation was not included as part of the create example for illustrative purposes.
The same criteria could have been supplied in the create request.
http://localhost:8080/ewws/EWUpdate?$KB=Demo&$table=contacts.
employees&$login=admin&$password=qwerty&$lang=en&id=358&_1576_company_name0=:
IBM
The following result will be returned in the case of successful modification of the record:
EWREST_full_name='John Doe';
EWREST__1576_company_name0='IBM';
EWREST_f_group_0='Service Manager';
EWREST_id='358';
EWREST__106_sw_description='Service Management Team';
EWREST__login='jdoe';
Here is an example of a JavaScript-based client that invokes the REST interface via AJAX:
REST - Delete
© 2022 Agiloft Inc. 125
REST - Delete
The EWDelete REST operation:
The deleteRule parameter defines one of the following strategies to be applied for dependent records:
The subs parameter is taken in consideration only if the deleteRule REPLACE_WITH_ANOTHER is specified and
should contain identifiers of records from the same table to be used as substitutes. For each record dependent on
the record being deleted, a record with a corresponding replacement key will become the parent one.
If the specific strategy fails the error message returned will suggest alternatives to be used.
If APPLY_DELETE_WHERE_POSSIBLE or DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK
strategies are used and the configuration of the knowledgebase allows it, a special "Fast Delete"
algorithm is used just as via the GUI.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Delete the record
updated in the REST Update Example.
http://localhost:8080/ewws/EWDelete?$KB=Demo
&$table=Contacts.Employees&$login=admin&$password=qwerty
&$lang=en&id=358&deleteRule=APPLY_DELETE_WHERE_POSSIBLE
Here is an example for a JavaScript-based client that invokes the REST interface via AJAX:
REST - Select
© 2022 Agiloft Inc. 127
REST - Select
The EWSelect REST operation:
Performs a select on the specified table, implementing the Select operation of the REST interface.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview. Additionally, the URL must contain the where clause of the SELECT query to be
performed. All parameters must be properly URL-encoded.
Supported Content-Type : application/x-www-form-urlencoded
This returns the number of records found, and an encoded data list of record IDs, if any were found.
Only one SQL statement is accepted per call. The API automatically and transparently applies security filters to
prevent unauthorized access to information.
These SQL queries operate on the database level and should be written in terms of dbname column names
as visible in the in the Table Wizard in the GUI. Some values for high-level complex field types may have
very different representation from what is seen in the GUI. For example, see REST Interface -
GetChoiceLineId for the special handling of choice values.
The SQL query supplied should only include the 'where' clause from a construct similar to:
This returns the number of records found, and an encoded data list of record ID, if any were found.
It is possible to limit the number of records returned through means available to the underlying database used by
your instance of Agiloft . For example "limit 0,200" for MySQL will limit the number of the returned records to 200.
It is not possible currently to influence the sequence in which records are returned. They are returned in the
effectively arbitrary default order returned by the underlying database. If you wish to impose a sort order on the
record sequence, consider using a EWSearch call instead. This will allow you to use a saved search to define a sort
order.
Example 1
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". List the Service
Request records assigned to 'Ralph Knowles'.
http://localhost:8080/ewws
/EWSelect?$KB=Demo&$login=admin&$password=qwerty&$table=helpdesk_case&$lang=en&where=assi
EWREST_id_length = '0';
EWREST_id_length = '3';
EWREST_id_0 = '150';
EWREST_id_1 = '169';
EWREST_id_2 = '325';
Example 2
Assume an instance of Agiloft is available on localhost, port 8080 and Demo KB. List the records with
summaries containing word 'new’ (summary like '%new%').
http://localhost:8080/ewws
/EWSelect?$KB=Demo&$login=admin&$password=qwerty&$table=helpdesk_case&$lang=en&where=summ
EWREST_id_length = '0';
The following result will be returned in the case of seven records being found:
EWREST_id_length = '7';
EWREST_id_0 = '145';
EWREST_id_1 = '147';
EWREST_id_2 = '148';
EWREST_id_3 = '149';
EWREST_id_4 = '151';
EWREST_id_5 = '298';
EWREST_id_6 = '318';
Here is an example for a JavaScript-based client that invokes the REST interface via AJAX:
REST - Search
© 2022 Agiloft Inc. 130
REST - Search
The EWSearch REST operation:
Runs a saved search or an ad hoc query on the specified table, implementing a search-like operation of the
REST interface.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface. The name of the saved search or an ad hoc query may be included in the call. This operation
allows retrieval of results page by page (pagination) using "page" and "limit" parameters.
Supported Content-Type : application/x-www-form-urlencoded
Returns the number of records found, and encoded sets of field values - one for each record found. If
pagination is being used, the number of records is in the context of the current page and the specified page
size.
Each set will include the ID and the fields used by the table and its subtables to define record ownership,
such as requester_login in the examples below. Additional fields can be added to the list by specifying a
series of 'field' parameters with logical field names.
Ad Hoc Searches
In opposition to saved searches, ad hoc searches are limited to run-time queries using the 'query' clause. An
example of such a REST query is shown below using the conditions &query=summary~='test'%26%
26priority=3:
http://localhost:8080/ewws
/EWSearch?$KB=Demo&$table=case&$login=admin&$password=qwerty&$lang=en&query=summary~=
'test'%26%26priority=3
To search on multiple fields, use a single query parameter that contains all the fields. Connect each field
using %26%26, which equates to "&&" or and, or using %7C%7C, which equates to "||" or or. Surround
each search value in single quotes, and if a field label contains spaces, surround that field label in single
quotes as well.
Pagination
© 2022 Agiloft Inc. 131
Pagination
Page numbers start with 0 (zero). A page size limit value of zero indicates "all records" and so all records
are returned on page 0 when limit 0 is specified; otherwise with a non-zero page number an empty result is
returned, meaning no records.
When a page is not found, an empty result is returned.
A call using pagination always returns a page worth of data. However, to truly take advantage of pagination
all other parameters must remain the same. If the table, fields, saved search, query or limit on a subsequent
call is different from the previous one, the underlying query is automatically rebuilt and re-run.
As such only one "open" query is allowed per client session. If the client requires multiple queries to be
iterated in parallel, the client code should create multiple sessions using the same login credentials.
This method doesn't support multi-threading – the client is responsible for restricting access to a single
thread; i.e. one client thread = one session = one open query.
The query will remain "open" until the session is closed, that is an explicit logout is performed by the client or
session timeout occurs, or the application server discards the underlying low-level objects as a result of
resource management. In this case the query will be rebuilt and re-run automatically on the next call.
If the query is rebuilt and re-run, the result of the next call may not be fully consistent with the results of the
previous call, as the underlying data may have changed – for example, a record was deleted, or the sort
order for the search in question was changed. Therefore, the dataset may appear to have "holes" and/or the
logical page boundaries may shift when iterating the query page by page.
At this time the REST interface creates a new session and performs an explicit logout for each call. As such,
though pagination is available, the query will always be rebuilt and rerun. The ability to issue multiple REST
calls within a single session, similar to the SOAP interface, is in development.
Assume an instance of Agiloft is available on localhost, port 8080 and the knowledgebase is called 'Demo'.
List the Service Request records that correspond to the saved search 'C: Status is Closed'. Additionally,
only return those that have High priority.
http://localhost:8080/ewws
/EWSearch?$KB=Demo&$login=admin&$password=qwerty&$table=helpdesk_case&$lang=en&search=C%3
EWREST_id_length = '0';
If no return fields are specified in the request, it will return the ID, type fields and requester login
field, based on the record ownership defined for the Service Request table, based on the
permissions definition for the Service Request table.
EWREST_length = '4';
EWREST_login_0='ewsystem';
EWREST_type_0='helpdesk_case';
EWREST_id_0='318';
EWREST_login_1='ewsystem';
EWREST_type_1='helpdesk_case';
EWREST_id_1='151';
EWREST_login_2='ewsystem';
EWREST_type_2='helpdesk_case';
EWREST_id_2='146';
EWREST_login_3='internal';
EWREST_type_3='helpdesk_case';
EWREST_id_3='145';
Example 2
Assume an instance of Agiloft is available on localhost, port 8080 and Demo KB. Now we want to retrieve
particular fields.
http://localhost:8080/ewws
/EWSearch?$KB=Demo&$login=admin&$password=qwerty&$table=helpdesk_case&$lang=en&search=C%3
EWREST_id_length = '0';
The following result will be returned in the case of four records being found:
EWREST_length = '4';
EWREST_summary_0='Here is a new service request with some tasks';
EWREST_priority_0='High';
EWREST_summary_1='New Employee Setup for Patricia Smith';
EWREST_priority_1='High';
EWREST_summary_2='Upgrading Our Software';
EWREST_priority_2='High';
EWREST_summary_3='Need New Wireless Card for Laptop';
EWREST_priority_3='High';
Assume an instance of Agiloft is available on localhost, port 8080 and Demo KB. Now we want to retrieve
particular fields in addition to the default ones as per Example 2.
We are retrieving data in pages 2 records at a time and are interested in the 2nd page only. Page numbers
counts from 0.
http://localhost:8080/ewws
/EWSearch?$KB=Demo&$login=admin&$password=qwerty&$table=helpdesk_case&$lang=en&search=C%3
EWREST_id_length = '0';
The following result is returned in the case of four records found in total, showing last two ones:
EWREST_length = '2';
EWREST_summary_0='Upgrading Our Software';
EWREST_priority_0='High';
EWREST_summary_1='Need New Wireless Card for Laptop';
EWREST_priority_1='High';
Here is an example for a JavaScript-based client that invokes the REST interface via AJAX:
REST - GetChoiceLineId
© 2022 Agiloft Inc. 135
REST - GetChoiceLineId
The EWGetChoiceLineID REST operation:
Obtains the internal identifier that corresponds to a choice value for use in SQL-based expressions.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview. Additionally, the URL must contain the name of the choice field and the choice list
element value.
Supported Content-Type: application/x-www-form-urlencoded
This returns the identifier of the choice list element suitable to use in EWSelect.
All parameters including the choice text value must be properly URL-encoded.
Use EWGetChoiceLineId to obtain the internal identifier that corresponds to the choice text value for further use
in SQL-based expressions. This is needed for the "where" parameter of EWSelectFromTable or the query text
when forging links to other tables via Linked Field sets.
SQL-based expressions are evaluated on the database level where choice values are stored as identifiers. These
identifiers change if the knowledgebase is copied or re-imported, or the values in the choice list are re-created. It is
therefore advisable to use the
Use the EWGetChoiceLineId call to obtain the ID value at runtime immediately before the SQL call.
If the factors that trigger the change of the internal choice identifiers, for instance knowledgebase copy or re-
import, or choice list values are re-created, are detected on the client, then the values returned by this call
can be safely cached on a per KB basis between multiple calls.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Obtain the
identifier for choice value 'High' for the field 'Priority' in the table 'Case'.
http://localhost:8080/ewws
/EWGetChoiceLineId?$KB=Demo&$login=admin&$password=qwerty&$table=case&$lang=en&field=prio
If there were no matching choice list values found, the HTTP code 400 will be returned with a brief
explanation of the problem.
The following result will be returned in the case of the text value being successfully translated into a
numeric identifier:
EWREST_choiceLineId = '1';
REST - Attach
© 2022 Agiloft Inc. 137
REST - Attach
The EWAttach REST operation:
Attaches the file passed in the body of the request to the specified field of the record identified by the
supplied primary key.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview. Additionally, the URL must contain the identifier of the record, the name of the field to
attach the file to and the file name to use. The file content is passed in the body of the request.
Supported Content-Type: multipart/form-data
Returns the current number of files attached to the specified field.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Attach a file to the
field "someField" in record 1234 of table "someTable" with a file name as testfile.zip.
The request has to be issued via the PUT HTTP method where the body contains the content of the file to
be attached.
http://localhost:8080/ewws
/EWAttach?$KB=Demo$table=someTable&$login=admin&$password=qwerty&id=1234&field=someField&
EWREST_someField.length='1';
Example code
Removes the attached file specified by the position in the named field of the record identified by the supplied
key.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview. The URL must contain the identifier of the record, the name of the field to remove the file
from and the position of the file in the field.
Supported Content-Type: application/x-www-form-urlencoded
Returns the number of files remaining attached in the field.
Here is an example for a JavaScript-based client that opens the REST interface via AJAX:
Returns the attached file specified by position from the named field in the record identified by the supplied
key.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview.
Returns the Content-Type used when attaching the original file.
Returns the file content in the body of the response via a GET or POST HTTP method.
The URL must contain the identifier of the record, the name of the field to retrieve the file from and
the position of the file in the field.
All parameters must be properly URL-encoded.
Here is an example of a JavaScript-based client that invokes the REST interface via AJAX:
REST - Login
© 2022 Agiloft Inc. 142
REST - Login
The EWLogin function is used to create JSON Web Tokens (JWT). These are access tokens used to retrieve data
with REST. With JWT, the user credentials are used only once, when the user requests the access token. After a
token is generated, the same token is used to authenticate the session until the token expires or is closed out.
Tokens must be refreshed before they expire. The default expiration time for the tokens is 15 minutes, but this value
can be changed by creating a Text or Long Integer global variable in your KB named token_expires_in.
Request Example
HTTP/2 200
server: nginx
Date: Fri, 24 Jan 2020 16:39:54 GMT
content-length: 482
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=31536000; includeSubDomains
{"access_token":"XXeyJhbGciOiJIUzI1NiJ9.
eyJzdWIiOiIxNTJfYWRtaW4iLCJyb2xlIjoiUkVTVCIsInNlYW5jZSI6IjQ0MTM4NzAiLCJleHAiOjE1OTY3MTAzMTUsIm
If you received the values in the example above, the header would be:
Example Header
Refresh Example
Logout Example
Executes an Action as an asynchronous call. The status of asynchronous calls can be checked with
EWAsyncStatus.
Must use a POST request.
Accepts the URL with parameters as per general URL conventions which can be viewed in the REST
Interface Overview. The URL must contain the table name, the ID of the record, and the name of the field
that contains the action button.
Supported Content-Type: application/x-www-form-urlencoded
Returns the record ID and callback ID.
EWREST_id='82';
EWREST_EWCALLBACK_ID='10100_1';
Checks the execution status of an asynchronous call, such as REST - Action Button, REST - Update, or
REST - Create.
Must use a POST or GET request.
Accepts the URL with parameters as per general URL and record data conventions, both of which can be
viewed in the REST Interface Overview. The URL must include the table name and the callback identifier
received by the asynchronous operation being checked.
Supported Content-Type: application/x-www-form-urlencoded
Returns the response code for the call's execution status:
200: Operation completed successfully.
201: The request is queued for execution.
202: Operation in progress.
501: Operation failed.
523: No information about the given callback ID.
REST - Table
© 2022 Agiloft Inc. 147
REST - Table
The EWTable operation...
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Retrieve a list of
the tables and fields, and include information about any linked fields.
After obtaining an authorization token with JWT, the following request is issued:
http://localhost:8080/ewws/EWTable/.json?$KB=Demo&includelinkedinfo=true
{
"success": true,
"message": "",
"result": {
"tables": [
{
"label": "WMI Sample",
"logicalName": "wmi_sample",
"fields": [
{
"columnLabel": "ID",
"columnName": "id",
"columnType": "BIGINT",
"columnTypeDomain":"swautoincrementfield"
Returns details about saved searches defined for a table. It returns the name and label of the search; the
saved search identifier in the Agiloft database; and the description of the search.
Supports GET and POST requests. For POST requests, the table parameter can be specified in the POST
Request body instead of the URL parameter ( ?$table=<logical_name_of_the_table>).
Accepts the URL with parameters as per general URL conventions and record data encoded as per Data
Encoding, both of which can be viewed in the REST Interface.
Must be used with REST - Login or OAuth 2.0 authorization.
Supported Content-Type:
For requests: application/x-www-form-urlencoded
For returns: application/json
JSON is the only output format supported, and you must use /.json after EWSavedSearch in the URL.
The parameter $table is mandatory and must use the logical name of a table in the KB.
This operation is never used asynchronously.
Example
Assume an instance of Agiloft is available on localhost, port 8080 and is called "Demo". Retrieve a list of
saved searches for the Contract table, using its logical name of contract.
http://localhost:8080/ewws/EWSavedSearch/.json?$table=contract
{
"success": true,
"message": "",
"result": [
{
"label": "R:Renewal date is tomorrow and Renewal Type is not one-time",
"name":"contract|r: renewal date is tomorrow",
"id": 265185,
"description":""
},
{
"label": "R:Contract is expiring tomorrow",
Agiloft's implementation of OAuth2 complies with the OAuth 2.0 specification and provides a variety of security
benefits, as described in RFC 6749.
Prerequisites
This feature works only with native Agiloft users; this is not compatible with LDAP.
1. Click the Setup gear in the top-right corner and go to Integration > OAuth2 Client Setup.
2. Click Deploy or Configure:
Deploy: The OAuth2 client hasn't been deployed yet. Clicking Deploy configures the OAuth2 client for
use in your KB, which can take a few minutes to complete.
Configure: The OAuth2 client has already been deployed. Clicking Configure takes you to the API
Application screen.
3. On the API Application screen, click New to open the API Application Settings wizard.
4. Complete the required fields:
a. Name: The name that the application uses with Agiloft's REST API.
b. Display Name: The name of the application that appears in the KB.
If you need to block your application for any reason, click Disable, which replaces Enable after it's
been clicked.
Changes to the values in the Associate this Application with Contact ID field or the Redirect URI field can
sometimes block your application. If this happens, recreate the application with the correct settings.
Authorization Request
The OAuth process starts with the client directing the user's browser to make a request to the /ewws/oauth
endpoint, where the Content-Type must be in the form application/x-www-form-urlencoded. The request
includes the following query string parameters:
response_type code Yes Defines the response type and must always have a
value of code.
redirect_uri Absolute URI defined Yes Defines where the user is redirected at the end of
on the API Application the authorization process. The value must belong to
Settings wizard, such the set of values specified on API Application
as Settings wizard.
https://example.
agiloft.com
scope permissions_for: Yes Defines the permissions that the user is asked to
{CONTACT_ID} approve, which are determined by the Contact ID
defined in the API Application Settings wizard. For
example, if the Associate this Application with
Contact ID field is set to 222, then this parameter
should be set to permissions_for:222.
state Any string Recommended Denotes a value that is returned to the client as a
parameter at the end of the authorization process,
which verifies the validity of the request. Although
this parameter is not required, we highly
recommended using it to protect against cross-site
request forgery (CSRF), as described in RFC 6749.
The authorization request asks the user to authenticate themselves and then authorize the request, which redirects
the user to the URI value defined for the redirect_uri parameter.
Successful Request
If the request succeeds, the following query string parameters are sent to the client in response:
Parameter Value
state Returns the value of the state parameter from the authorization request.
code Indicates the authorization code for use in the upcoming access token request. This code is
valid for five minutes.
api_access_point Defines the base URI for all REST API calls that use an access token, if any. If this value is
empty, use the base URI for your KB's server.
Failed Request
If the request fails, the following query string parameters are sent to the client in response:
Parameter Value
error Defines the error code. See below for a description of each error code.
Code Description
unauthorized_client The client is not authorized to request an authorization code using this method.
unsupported_response_type The authorization server does not support obtaining an authorization code using
this method.
grant_type authorization_code Yes Defines the grant type, which must always be
authorization_code.
code The authorization code Yes Confirms that the previous step was successful.
obtained in the previous step
redirect_uri Must match the value used in Yes This value must belong to the set of values
the previous step specified in API Application Settings wizard.
grant_type=authorization_code&
code=EFLJHELFH23487402387LKFEHJFEHF&
client_id=Bvn7k4fIdMEZQrJJ7ZCIQgErlTDbX9L73LThA5YA4W0%3D&
redirect_uri=https%3A%2F%2Fptop.only.wip.la%3A443%2Fhttps%2Ftest.com%2Freceiver
Parameter Value
access_token The access token, which can be used as an authorization header in the Agiloft REST API.
refresh_token The refresh token, which can be used to get a new access token. Refresh tokens expire after
28 days of inactivity.
{
"access_token":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyX2FkbWluI",
"refresh_token":"LKHEFOP932875KJGKJG32423542LHLKHFD_FDKLJ.OJ==",
"token_type":"Bearer",
"expires_in":15
}
Refresh Request
At any time the client can use the refresh token to receive a new access token. To start a request, the client makes
an HTTP POST to the /ewws/otoken endpoint by using the api_access_point value retrieved during the
authorization request. The following parameters are used:
md5_secret The first 20 characters of the Client Secret, obtained Yes Authenticates the
from API Application Settings wizard application.
refresh_token Refresh token received during the previous step Yes Defines the refresh
token.
grant_type=refresh_token&
md5_secret=F64EC9CE9852EE154696&
refresh_token=LKHEFOP932875KJGKJG32423542LHLKHFD_FDKLJ.OJ%3D%3D
The refresh token can then be used to generate a new access token. The server returns a JSON array with the
following keys:
Parameter Value
access_token The access token, which can be used as an authorization header in the Agiloft REST API.
Example Response
{
"access_token":"wehRT74iOiJIUzI1NiJ0.weJzdWIiOiIyX2FkbWluI",
"token_type":"Bearer",
"expires_in":15
}
Revoking Tokens
Access tokens and refresh tokens can be revoked at any time by the authorizing user. REST operations cannot be
performed with a token that has been revoked. If an access token is revoked and it has a corresponding refresh
token, the refresh token is also revoked. If a refresh token is revoked, all the access tokens issued from that refresh
token are also revoked.
Refresh tokens can be revoked by making a POST call to the /ewws/orevoke endpoint by using the
api_access_point value retrieved during the authorization request. The following parameters are used:
revoke_for OAuth2 refresh token or Client Secret key from API Yes Defines the refresh token
Application Settings wizard or Client Secret Key.
revoke_for=LKHEFOP932875KJGKJG32423542LHLKHFD_FDKLJ.OJ%3D%3D
{
"error" : "INVALID_REQUEST",
"error_description" : "Invalid token."
}
In some cases, when designing integrations in accordance with RFC, you might need to bypass the Content-Type
header check requirement for REST OAuth2 requests. This method is not recommended, because the Content-
Type header check is a security measure, so use this only as a last resort.
1. In the admin console, create a new Text global variable named ignore_oauth_content_type and set it to the
IDs of the KBs where you want to bypass the check. For example, you might enter 66,77,13 to bypass three
KBs with those IDs.
2. In the KB, go to Setup > System > Manage Global Variables and create a Choice global variable named
disable_content_type_validation_at_api_oauth_login. Assign it the Yes/No choice list and set it to Yes.
Now, with both variables set, /ewws/oauth, /ewws/otoken, and /ewws/orevoke operations won't require a
specific Content-Type header.
Receives and parses XML messages from Agiloft, either directly or from the remote proxy
Processes the messages, usually by accessing external system (XS) data
Frames an XML response and passes it back to Agiloft.
The Agiloft synchronization subsystem provides facilities for automatic synchronization between Agiloft tables and
the corresponding records in some external system. Synchronization can be bidirectional or unidirectional in either
direction. Agiloft comes with pre-built ESAs for common systems such as Exchange and Excel. Additional ESAs
can be created by customers in Java, or in the language of their choice.
Note: one major advantage to developing the ESA in Java is that it can easily be linked with the
bundled Agiloft Remote Proxy that handles the communication with the sync subsystem. If the ESA is developed in
another language, you must use the standard I/O streams to communicate with the Remote Proxy.
An addition is necessary for HTTPs ESA's, which can initiate calls beginning a sync to the Agiloft Helper API. A
command-line ESA can't do that, because it is only run by sync when Agiloft decides that it is time to synchronize.
Such an ESA can't pass a message to Helper API outside of the sync cycle.
ESA Properties
To be synchronized with Agiloft, an external system must have these properties:
It must be possible to obtain the modification timestamps for each record. This timestamp is typically
contained in the record itself, but this is not absolutely necessary.
Record IDs should not be changed, or at least there must be a way to determine any record ID as it was in
the last sync. Record IDs can be remapped during sync, but the old ID must be made known to the ESA in
order to track record matches.
Records from the external system, as presented during sync by the ESA, must be grouped by structures,
which are mapped on a 1-to-1 basis to Agiloft types or tables, such as People/Employee. An ESA structure
can be a table, a folder, a file or anything else that logically groups records in the external system. Within a
given structure, external records must use the same set of field mappings.
Possible Configurations
This diagram shows a few possible configurations for the external system to work with Agiloft.
ESA A is located on the external host, communicating with the external system over some possibly
proprietary protocol and communicating with Agiloft over HTTPs, as if over the internet.
ESA B is a command-line (CL) application, also on the external system host, but is managed (launched) by
an ESA remote proxy which, in turn, communicates with Agiloft over HTTPs. The ESA Remote Proxy uses
standard I/O streams for message exchanges with the ESA.
ESA X and ESA Y are similar to ESA A and ESA B, respectively, but are located on the Agiloft host. ESA X
is run within its external system and communicates with Agiloft over HTTPs, as if over the internet. ESA Y is
run and communicates directly with Agiloft through STDIO.
If your external system is a server or a continually running daemon-like process, implementing the ESA as a
module of the system might be a better option, since you can control the life cycle of the ESA. In this case,
the ESA should connect with Agiloft over HTTPS.
For an application that is not always running, use the command-line form.
If you need to trigger synchronization based upon external system events, use an HTTPS and possibly
daemon-like ESA. A command-line ESA cannot connect to Agiloft on its own, and will only be triggered
with Agiloft begins a synchronization.
If you don't need the external system to ever trigger a synchronization, develop your ESA as a command-line
application, using standard I/O communication with the XS. If the ESA is not on the Agiloft host, an ESA
Remote Proxy will be needed for it to interface with Agiloft, but the ESA will be simpler to develop.
The Agiloft ESA for QuickBooks must run on the Agiloft host, but otherwise ESAs can fit into any of the
above contexts. Firewall configurations will often determine the most convenient deployments, which will in
turn dictate certain sync configuration options.
Once an ESA has been designed, it can communicate with systems of the same type, not just with a single
server. For instance, once an Exchange ESA is written, you may set up synchronizations with many
Exchange servers, not just with that particular Exchange installation.
Java classes for message handling are available for any ESA that is to be developed in Java. The ESA
Interface Reference topic describes the extensive reusable Java classes available, and provides a complete
ESA in Java.
Communications use the XML-RPC model, where XML messages are sent to and from
the Agiloft server. However, when an ESA is remote, the communication flow is actually reversed in order to
allow communication through firewalls. The remote ESA actively polls for the next XML-RPC message on
the Agiloft server. Logically though, the communication is the same because the ESA only performs
commands issued by the sync subsystem and is therefore passive. This polling is handled by the Java
support libraries provided by Agiloft and need only be considered if you are developing an ESA using a
programming language other than Java.
A remote Command-line ESA can never initiate a syncn cycle, but an HTTPs one can do so, by a call on
the Agiloft Helper API. In this case the initial call is startSync(HelperAPI). Agiloft then responds with a
startSync(ESA) and other calls to the ESA, finishing communication by a void result of startSync(Helper
API).
Call ID
Every call has a mandatory call-id attribute. This attribute should be set by the caller to a positive integer value. It is
recommended but not obligatory that this value would be incremented on each call. A result of a call has a
"response-to" attribute, filled by the call-id value of the call, to which this result belongs. This simple mechanism
allows monitoring of message flow and detection of communication or application failures.
The ESA must provide the Agiloft sync subsystem with some meta-information about your external system, such
as the list of structures and the fields in each table that are to be mapped. When setting up the synchronization
configuration, the Agiloft administrator - who does not have to be the ESA developer - maps Agiloft table fields to
your external system fields, thus establishing the relationship between them.
ESA Parameters
An ESA usually requires some initialization parameters in order to do the synchronization, such as external system
name, port, schema name, and so on. For the most part, synchronizations are quite simple and it would place an
unnecessary burden on the Agiloft administrator to manage the process through configuration files or special tools.
The Agiloft subsystem core can manage the following processes:
The ESA provides the Agiloft sync subsystem with a list of parameters it needs, including parameter names
and types. Several simple types are supported, such as string inputs, choice fields and so on.
The Agiloft administrator sets up the sync configuration through an Agiloft GUI that allows the admin to map
these parameters to Agiloft tables so that synchronization can take place.
At run time, the ESA obtains these stored parameter values, using the parameter name as the key.
This mechanism provides a simple way to manage configuration and synchronization data. Your ESA may or may
not use this facility. It is not mandatory, but it is recommended to make the configuration easier for
the Agiloft administrator.
Sync Configuration
Before running a synchronization, the Agiloft administrator must first set up the configuration. This is done inside
the Agiloft knowledgebase, and includes the following aspects:
Pseudocode
Pseudocode for an ESA is:
Schematic Examples
This section contains examples of XML messages in the ESA:
Calls to ESA
All messages are framed by <sync> and <esa-call> elements and are prepended by an XML declaration. Actual
messages look like this:
<xs:element
name="sync">
<xs:complexType>
<xs:choice maxOccurs="1">
<xs:sequence>
<xs:element name="esa-call" type="esaCallType"/>
</xs:sequence>
<xs:element name="api-call" type="apiCallType"/>
</xs:sequence>
<xs:sequence>
<xs:element name="result" type="resultType"/>
</xs:sequence>
</xs:choice>
<!--Calls-->
<xs:complexType name="callType" abstract="true">
<xs:attribute name="call-id" use="required" type="xs:nonNegativeInteger"/>
</xs:complexType>
<xs:complexType name="esaCallType">
<xs:complexContent>
<xs:extension base="callType">
<xs:group ref="esa-calls"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="apiCallType">
<xs:complexContent>
<xs:extension base="callType">
<xs:group ref="helper-api-calls"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!--Start Sync-->
<xs:complexType name="startSyncEsa">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.configure-->
<xs:complexType name="configure">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
<xs:element name="force" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
<!--Esa.readDataPage -->
<xs:complexType name="readDataPage">
<xs:sequence>
<xs:element name="cursor-id" type="xs:string"/>
<xs:element name="page-index" type="xs:nonNegativeInteger" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!--Esa.read-->
<xs:complexType name="read">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="id" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.create-->
<xs:complexType name="create">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="record" type="recordType"/>
<!--Esa.update-->
<xs:complexType name="update">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="last-seen" type="xs:dateTime" id="last-seen-update"/>
<xs:element name="record" type="recordType"/>
</xs:sequence>
</xs:complexType>
<!--Esa.delete-->
<xs:complexType name="delete">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="last-seen" id="last-seen-delete" type="xs:dateTime"/>
<xs:element name="id" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.countRange -->
<xs:complexType name="countRange">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="min-id" type="xs:string"/>
<xs:element name="max-id" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.checkDelayedCreate -->
<xs:complexType name="checkDelayedCreate">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="token" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.checkDelayedUpdate -->
<xs:complexType name="checkDelayedUpdate">
<xs:sequence>
<xs:element name="structure" type="xs:string"/>
<xs:element name="token" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Esa.syncErrorNotify -->
<xs:complexType name="syncErrorNotify">
<xs:sequence>
<xs:element name="error" type="xs:string"/>
</xs:sequence>
<!--Helper API-->
<!--Start Sync-->
<xs:complexType name="startSyncApi">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
</xs:sequence>
<xs:attribute name="asynchronous" use="required" type="xs:boolean"/>
</xs:complexType>
<!--HelperApi.getParameter -->
<xs:complexType name="getParameter">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--HelperApi.getPollPeriod -->
<xs:complexType name="getPollPeriod">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--HelperApi.trackRecordDeletion -->
<xs:complexType name="trackRecordDeletion">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
<xs:element name="structure" type="xs:string"/>
<xs:element name="id" type="xs:string"/>
<xs:element name="time" type="xs:dateTime" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!--HelperApi.detectDeleted -->
<xs:complexType name="detectDeleted">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
<xs:element name="after" type="xs:dateTime"/>
<xs:element name="structure" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--HelperApi.isKnownID -->
<xs:complexType name="isKnownID">
<xs:sequence>
<!--HelperApi.enumerateKnownIDs -->
<xs:complexType name="enumerateKnownIDs">
<xs:sequence>
<xs:element name="external-system-id" type="xs:string"/>
<xs:element name="known-before" type="xs:dateTime"/>
<xs:element name="structure" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--Data types-->
<!--External Record-->
<xs:complexType name="recordType">
<xs:sequence>
<!--Field values-->
<xs:element name="field" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<!--Collections-->
<xs:element name="collection" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="record" type="recordType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<!--Record Attributes-->
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="old-id" type="xs:string"/>
<xs:attribute name="modified" type="xs:dateTime"/>
<xs:attribute name="new" type="xs:boolean"/>
</xs:complexType>
<!--Exception Descriptor-->
<xs:simpleType name="exceptionTypeEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="general"/>
<xs:enumeration value="record"/>
<xs:enumeration value="recorddelayed"/>
<xs:enumeration value="configuration"/>
<xs:enumeration value="alreadyconfigured"/>
<xs:enumeration value="optlockfailed"/>
<xs:enumeration value="concurrentdelete"/>
<xs:enumeration value="skipstructure"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="exceptionType">
<xs:sequence>
<xs:element name="message" type="xs:string"/>
<xs:element name="trace" type="xs:string"/>
<xs:element name="configured-to" type="xs:string" minOccurs="0"/>
<xs:element name="external-id" type="xs:string" minOccurs="0"/>
<xs:element name="modified-at" type="xs:dateTime" minOccurs="0"/>
<xs:element name="delay-token" type="xs:string" minOccurs="0"/>
<xs:element name="structure-name" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="type" type="exceptionTypeEnum" use="required"/>
</xs:complexType>
<!--Cursor Descriptor-->
<xs:complexType name="cursorType">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="page-size" type="xs:positiveInteger" use="required"/>
<xs:attribute name="number-of-pages" type="xs:nonNegativeInteger" use="required"
/>
</xs:complexType>
<!--Result-->
<xs:complexType name="resultType">
<xs:choice minOccurs="0"> <!-- Result can be empty as well -->
<!--Exception Result-->
<xs:sequence>
<xs:element name="exception" type="exceptionType"/>
</xs:sequence>
<!--Record list-->
<xs:sequence>
<xs:element name="record" type="recordType" maxOccurs="unbounded"/>
</xs:sequence>
<!--Structure list-->
<!--Fields list-->
<xs:sequence>
<xs:element name="field" type="externalFieldType" maxOccurs="unbounded"/>
</xs:sequence>
<!--Relations list-->
<xs:sequence>
<xs:element name="relation" type="externalRelationType" maxOccurs="unbounded"/>
</xs:sequence>
<!--Collections list-->
<xs:sequence>
<xs:element name="collection" type="externalCollectionType" maxOccurs="unbounded"
/>
</xs:sequence>
<!--Cursor result-->
<xs:sequence>
<xs:element name="cursor" type="cursorType"/>
</xs:sequence>
<!--ID list-->
<xs:sequence>
<xs:element name="id" maxOccurs="unbounded" type="xs:string">
</xs:element>
</xs:sequence>
External Records
External records are mapped to XML snippets with the form:
<record id="id-value"
old-id="old-id-value"
modified="modificationtimestamp">
<field name="field-name"> field value </field>
...
<field name="field-name">...</field>
<relation id="relation-id>
<related>related-object-id</related>
...
<related>related-object-id</related>
</relation>
...
<relation id="relation-id>
...
</relation>
<collection>
<record> tags, describing collection members.
</collection>
</record>
External Structures
External Structures are mapped to XML snippets with the form:
<collection id="collection-id"
screen-name="screen-name"
required="boolean-value"
updatable="boolean-value"
updatableOnCreate="boolean-value"/>
External Fields
External Fields are mapped to XML snippets with the form:
<field name="name-value"
screen-name="screen-name"
type="xsd-type-name"
required="true/false"
identifying="true/false"
updatable="true/false"
updatableOnCreate=" true/false"
maxLength="maximum length of string representation">
<!-- If a field is of enumeration type, it should have type "ew:enum"
and contain one or more nested <enum-value> elements: -->
<enum-value>
<value>value</value>
<screen-name>
name to be used in mapping editor
</screen-name>
</enum-value>
</field>
External Relations
External Relations are mapped to XML snippets with the form:
<relation id="relation-id"
related="related-structure-name"
screen-name="screen-name"
<esa-parameter
name="string-name"
type="single | radio | multi | expandable | xml"
required="true | false">
<label>Label to show above input</label>
<hint>Hint to show on the left</hint>
<item-type>
xsd:string | xsd:integer | xsd:double | xsd:Boolean
</item-type>
<default-value>
String-value | Integer-value | Double-value | Boolean-value
</default-value>
<!-- For parameters of type "radio", "multi" and
"expandable", a number of nested <enum-value>
elements should follow: -->
<enum-value>
<value>value</value>
<screen-name>
name to be used in mapping editor
</screen-name>
</enum-value>
</esa-parameter>
Cursors
Cursors are mapped to XML snippets with the form:
<cursor id="cursor-id"
page-size="records-per-page"
number-of-pages="number-of-data-pages"/>
The ESA interface is the same for other interfaces, so the class and method comments can still be used.
checkDelayedCreate
Signature ExternalRecord checkDelayedCreate(String structure, String token)
If a record creation has been delayed by the ESA, the sync core will call this method to get the
actual operation result
Rationale For some systems, it might be very inefficient to create records one by one, in the order Sync
Core calls ESA. In this case, record creations can be delayed to form a single, or a few bulk
updates to the external system.
If such a delay is required, the create() method should schedule the creation and throw
EsaRecordDelayedException exception. After Sync Core performs all create() calls, it
will call the checkDelayedCreate() method to get the actual creation result.
The moment ESA does the actual External System update is not important for the Sync Core.
Common strategies are to either wait until checkDelayedCreate() is called, accumulating all
creates or to do some more intermediate updates, one for every N create() calls.
Parameters
structure - structure to check operation for
token - operation token, as supplied within EsaRecordDelayedException
Exceptions
EsaRecordException if the delayed operation fails for the record
EsaRecordDelayedException if the operation is to be delayed again, or not yet done.
Sync will then re-call this method later.
public ExternalRecord
checkDelayedCreate(String structure, String token) throws RemoteException,
EsaException, EsaRecordException {
checkDelayedDelete
Signature Date checkDelayedUpdate(String structure, String token)
If a record delete has been delayed by the ESA, the sync core will call this method to get the
actual operation result
Parameters
structure - structure to check operation for
token - operation token, as supplied within EsaRecordDelayedException
Returns Nothing
Exceptions
EsaRecordException if delayed operation fails for the record
OptimisticLockFailureException if the record is modified after the lastSeen
timestamp. This means that a record was modified while sync was in progress.
EsaRecordDelayedException if the operation is to be delayed again, or not yet done.
Sync will then re-call this method later.
If a record update has been delayed by the ESA, the sync core will call this method to get the
actual operation result
Parameters
structure - structure to check operation for
token - operation token, as supplied within EsaRecordDelayedException
Exceptions
EsaRecordException if delayed operation fails for the record
OptimisticLockFailureException if record is modified after lastSeen timestamp -
this means that a record was modified while sync was in progress.
EsaRecordDelayedException if the operation is to be delayed again, or not yet done.
Sync will then re-call this method later.
closeCursor
Signature void closeCursor(String cursorID)
Description Closes the cursor. This call indicates that Agiloftdoesn’t need the cursor anymore and
guarantees that readDataPage will never be called for this cursor.
Returns Nothing
Exceptions None
Configure
Signature String configure(String externalSystemID, boolean force)
Description Indicates that the ESA is used by a Sync configuration. The method is called on the ESA when
a new Sync configuration is created.
Simple ESAs may just ignore this call and return the passed externalSystemID.
If, for any reason, the ESA should not be used by the configuration, the ESA should return null
unless the parameter 'force' is true. If force is true, ESA should adjust itself to be used by the
configuration.
Rationale A complex ESA may store additional per-configuration data or may require that only a single
sync configuration exist per ESA installation.
The configure() method provides a means to control this by notifying the ESA that it is about
to be used by a configuration with the given External System ID.
If the ESA returns null, the Agiloftadmin is asked to actively confirm that he wishes to use the
ESA with the configuration. If he confirms, configure() is called again, with the force
parameter set to true. ESA should treat this as a direct order from admin to be used with this
External System ID from now on. The ESA may throw a ConfigurationInvalidException
from the startSync() method if called with an old inactive External System ID.
Parameters
externalSystemID - External System ID as set in the sync configuration.
force - indicates that admin insists, confirms a warning, configuring the ESA for this
configuration.
Exceptions None
countRange
Signature int countRange(String externalStructure, String idMin, String idMax)
Description Counts the number of tickets, with IDs in range of [IDmin;IDmax] (inclusive). This method is a
callback for the HelperApi.detectDeleted(ExternalSystemAdapter, String,
String, java.util.Date) method and is only called if HelperApi.detectDeleted is
called by the ESA
Rationale This method is only called if the ESA calls the HelperApi.detectDeleted() method.
detectDeleted() is based on binary division, checking the numbers of records, whose IDs
fall in a range. Therefore, it needs to count the number of such external records through the
ESA.
Parameters
structure - structure to check records in
idMin - minimal value of ID range
idMax - maximum value of ID range
Returns Nothing
Exceptions None
Create
© 2022 Agiloft Inc. 187
Create
Signature ExternalRecord create(String structure, ExternalRecord values)
Rationale Sync core calls this method to create a new external record, matching a new Agiloft record.
Parameters
structure - structure to create record in
values - field and relations of the record to create
Returns Newly created record. In particular, the ID and Timestamp fields must be filled.
Exceptions
EsaRecordException if a record can’t be created,
EsaRecordDelayedException if ESA wants to delay the creation to form a bulk update.
Delete
Signature void delete(String structure, Date lastSeen, String pk)
Rationale Sync Core calls this method to propagate deletion of an Agiloftrecord to the external system.
Note: By default, deletions are not propagated at all. This can be changed within Sync
Configuration editor, when editing table mapping in the Field Mapping wizard
Parameters
structure - structure to delete record in
Returns Nothing
Exceptions
EsaRecordException if a record cannot be deleted,
EsaRecordDelayedException if ESA wants to delay the delete to form a bulk update.
OptimisticLockFailureException if record is modified after lastSeen timestamp;
this means that a record was modified while sync was in progress.
endSync
Signature void endSync()
Description Finishes the synchronization session. This call denotes the successful end of a synchronization.
ESA may clean up any resources, release connections and so on needed to perform a
synchronization.
See also Release.
Parameters None
Returns Nothing
getAllowedRunModes
Signature int getAllowedRunModes()
Description Returns Bit-OR'ed constants from RunModes, restricting synchronization run modes.
For example, RunModes.RUN_SYNC_ACTIONS bit-OR RunModes.
RUN_EXTERNAL_TRIGGERED would designate that ESA supports non-interactive
synchronizations by both Agiloft and External System requests.
Manual - this is when synchronization is manually triggered through the Agiloft GUI - the
Sync menu item in the table toolbar. The synchronization itself is always run in the
background, but some ESAs such as Google Contacts and Calendar ESA, require user
interaction and can only be run in this mode.
By Agiloft actions - Agiloft actions can be executed either by a rule or workflow which are
configurable for the admin.
For example, it is possible to set up a rule, running a sync action - an action type which runs
the synchronization with a given External System ID - every time a record is modified. This
allows for 'online' synchronizations. Another possibility is to set up a time-based rule to do
batch synchronization.
External Triggered - a synchronization can be triggered by calling the HelperApi.
startSync() method from outside Agiloft.
For some ESAs, it is impossible to initiate synchronization in any way other than by a
request from the External System. In this case, it is advisable to implement the ESA as an
HTTPS ESA, listen for an External System request in the system-specific manner and call
HelperApi.StartSync() when it is time to run sync. In this case
HttpXmlEsaTransport should be used as a transport in HelperApi. See also HelperApi
Interface.
This method tells The sync core which types of operations are suitable for the ESA. Most ESAs
are agnostic to the way synchronization is run and support all methods - RunModes.ANY.
Exceptions None
getCollections
Signature Set<ExternalCollection>getCollections(String structureOrCollection, Locale locale)
Rationale
Parameters
structureOrCollection - a collection to return fields for
locale - locale to use for message localization
Exceptions None
public Set<ExternalCollection>
getCollections(String structureOrCollection, Locale locale) throws
EsaException, RemoteException {
// Collections are used to represent hierarchical data and are something
// in between of relations and fields. It is a bit advanced concept.
// It is like relation, because it stores other records and is mapped to some EW
table.
// It is like field, because it is updated at once, as a field
// This ESA doesn't use them.
Set<ExternalCollection> result = new
HashSet<ExternalCollection>();
assert result != null;
return result;
}
Description Gets current UTC time of a remote system, if available. If it is impossible to determine current
time on the External System machine, this method should return null.
Rationale Sync subsystem uses this method to compute the time shift between the Agiloft server where
the sync subsystem is running and the External System. This is taken into account when
checking if the record is modified or not.
The shift is applied for all date-time "control" values passed to ESA – that is, timestamps in
getModified() / getDeleted(), record.modifiedAt, lastSeen parameter and so on.
The shift is not applied for converting record date/date-time/time fields inside ExternalRecord if
the record data values are not affected.
Parameters None
Exceptions None
getDeleted
Signature Set<String>getDeleted(String structure, Date since)
Description Gets IDs of records deleted in the external system after a given timestamp.
ESA may or may not pay attention to this timestamp. It is acceptable for the ESA to respond to
this call with all of the IDs ever deleted in the system - sync will handle this properly, but this will
result in more traffic between the ESA and Agiloft.
The timestamp corresponds to the time of the last successful invocation of getDeleted().
If an ESA accumulates deletion information, it may always return all accumulated IDs and purge
them after successful endSync().
The ESA may decide how to transfer data at runtime depending on the amount.
In any case, Agiloft will call getDeleted first and only call getDeletedPaged if getDeleted
returns NULL <nullValue>true</nullValue>
Rationale This method provides the sync subsystem with a list of deletions that happened in the External
System since the last sync.
Parameters
structure - structure to read
since - return records deleted since the timestamp
Exceptions None
getDeletedPaged
Signature Cursor getDeletedPaged(String structure, Date after)
This method is only called if the getDeleted call returned NULL (<nullValue>true</nullValue>).
Rationale getDeleted() may return a large amount of data. For an HTTPs ESA, this may cause problems
because of network timeouts and proxy server settings. In this case, it is advised to use the
getDeletedPaged() method.
Parameters
structure - structure to read
since - timestamp
Exceptions None
getDetailedReport
Signature String getDetailedReport ()
Description A detailed, debug level, progress report or null, if ESA does not provide this feature
This report is accessible when clicking 'To view raw log file, click here' in the synchronization
progress window.
Parameters None
Exceptions None
getFieldList
Signature Set<ExternalField>getFieldList(String structureOrCollection, Locale locale)
Rationale
Parameters
structureOrCollection - a collection to return fields
locale - locale to use for message localization
Exceptions None
public Set<ExternalField>
getFieldList(String structureOrCollection, Locale locale) throws EsaException,
RemoteException {ResourceBundle i18n = getResources("com.supportwizard.sync.
sampleesa.sampleesa", locale);
Set<ExternalField> result = new HashSet<ExternalField>();
// Switch on the logical structure name (ones, that were returned by
getStructureList())
if(CONTACTS.equals(structureOrCollection)) {
// Return fields for contacts.
// ID field. result.add(new ExternalField(
CONTACT_ID,
// Logical name. This one is used in ExternalRecord, passed to create()/update()
i18n.getString("contacts.id"),
// Localized name, shown to EW administrator when setting up field mappings
XsdType.INT,
// Type. This one is a numeric ID false,
// Not used for unknown records identification.
// If IDs are somewhat persistent among two systems (i.e. John Bull should have ID
#1 in both systems, it should be identifying field
// If IDs are allowed to differ, they usually will and we are better
// to match records on something more meaningful, such as full name
true,
// Required false, true,
// Not updatable except on create
20));
// No longer than 20 digits
// Note we have not specified
that the field is a primary key. PK value is
supplied separately with each record
// and is not a must o
expose the ID field at all.
// You may well do not, if you
don't want to map it and use its value somewhere
in EW
// Simpler full name and email
getModified
Signature Set<ExternalRecord>getModified(String structure, Date after)
Description Gets all records modified in the external system after the given timestamp. For some systems,
this may result in a very large amount of data to be transferred, especially for the initial sync. If
the ESA predicts such high traffic, it should return null from this method - not an empty set.
In this case, Agiloft will make use of the getModifiedPaged method. An ESA may decide how
to transfer data at runtime depending on the amount.
In any case, Agiloftwill call getModified first and only call getModifiedPaged if
getModified returns null - <nullValue>true</nullValue>.
Rationale To do the synchronization, the Sync subsystem must know which external records have been
modified since the last sync and read their content in order to update Agiloft records.
Parameters
structure - structure to read
after – timestamp, or null if all records are to be returned
Exceptions None
public Set<ExternalRecord>
getModified(final String structure, final Date after) throws EsaException,
RemoteException {
// Log debug info for troubleshooting
log.debug("getModified (" + structure + ", " + after + ")");
// Though it is not a must, having a "servant" class per table is usually
convenient.
// It is a pure implementation detail, but it is often convenient to organize ESA
in that way,
// avoiding lengthy IFs switches on structure name in in
getModified/getDeleted/create/update/delete methods
// Note: if meta-data is not hardcoded, it is usually a good idea to place
// meta-data reading (getFields(), getRelations(), getCollections()) to "servants"
as well.
TableServant servant = name2servant.get(structure);
Set<ExternalRecord> result = servant.getModified(after);
assert result != null;
return result;
}
getModifiedPaged
Signature Cursor getModifiedPaged(String structure, Date after)
Description Gets modified records in a paged manner. See the getModified description.
This method is only called if the getModified call returned NULL - <nullValue>true<
/nullValue>.
Rationale getModified() may return a large amount of data, especially for initial synchronization, when
all records are to be read. For an HTTPs ESA, this may cause problems because of network
timeouts and proxy server settings. In this case, it is advised to use getModifiedPaged
methods.
Exceptions None
getParametersMeta
Signature List<EsaParameterMeta>getParametersMeta(Locale locale)
Rationale Agiloft maintains ESA parameter values through the Sync Configuration wizard. To generate the
GUI, Agiloft must know the parameter name, type, etc. This method provides that information.
The Sync Configuration wizard ESA Parameters tab is constructed based on the information
returned by that method.
Exceptions None
public List<EsaParameterMeta>
getParametersMeta(Locale locale) throws EsaException, RemoteException {
List<EsaParameterMeta> result = new ArrayList<EsaParameterMeta>();
// Screen names can be localized, see resource properties bundle.
// A simpler ESA may just ignore "locale" parameter and return hard-coded names
ResourceBundle i18n = getResources("com.supportwizard.sync.sampleesa.sampleesa",
locale);
// There is a single parameter we need - where to store our files
result.add(new EsaParameterMeta(WORK_DIR,
// Logical name, used to obtain parameter value
EsaParameterValueType.TEXT,
// String
EsaParameterType.SINGLE,
getProgressReport
Signature String getProgressReport ()
Description HTML progress report, to the current moment or null, if ESA does not provide this feature. The
report should be provided in fully rendered HTML, starting with an <html>and ending with <
/html>
If an ESA provides this report, that is, returns non-null from this method, the report HTML text
completely replaces the standard progress screen. The ESA should provide the full-featured
progress GUI, including automatic page refresh and other GUI elements such as a Close button
if necessary.
Please see ProgressUIHelper utility class for organizing self-refreshing page. Please also
see Example 2 below.
ESA may also use this mechanism to interact with the user, providing a fully interactive GUI. In
this case, you should return RunModes.MANUAL from the getAllowedRunModes() method.
Parameters None
Exceptions None
getRelations
Signature Set<ExternalRelation>getRelations(String structureOrCollection, Locale locale)
Rationale Sync subsystem maintains a mapping of links between Agiloft tables - Linked Field Sets - and
relations of the mapped external structures. For example, a table of Widgets might include a
Purchased By field, which links to the prospect who purchased that Widget. To see the
mapping, edit Sync Configuration > Relations tab.
The list of External Relations in the GUI is the set returned by this ESA method.
Parameters
structureOrCollection - a collection to return fields
locale - Locale to use for message localization
Exceptions None
public Set<ExternalRelation>
getRelations(String structureOrCollection, Locale locale)
getStructureList
Signature Set<ExternalStructure>getStructureList(Locale locale)
Rationale The sync subsystem maintains the mapping between AL tables and external structures. You
may edit this mapping in the Sync Configuration wizard, mappings tab.
The list of External Structures in the GUI is the set returned by this ESA method.
Exceptions None
leaseCursor
Signature void leaseCursor(String cursorID)
Returns Nothing
Exceptions None
leaseSession
Signature void leaseSession()
Parameters None
Returns Nothing
Exceptions EsaException if the ESA has already expired due to internal timeout.
needSyncAgain
Signature boolean needSyncAgain()
Rationale Sometimes it might be necessary to re-run synchronization again after it just finished. For
example, a record update may trigger cascade-updates of other records, possibly after these
other records were synchronized. To propagate those changes back to Agiloft, it is necessary to
run synchronization once more.
In such cases, ESA should return true from this method.
Parameters None
Exceptions None
Rationale Sync subsystem may need to read a single external record. For example, this method is called if
a record was not updated during the last synchronization because of an error.
Parameters
structure - read record of that structure
pk - read record with that ID
readDataPage
Signature Set readDataPage(String cursorID, int pageIndex)
Description Gets data from a cursor. This method is used to actually access the data in the cursor.
Parameters
cursorID - cursor ID
pageIndex - data page number, 0 based
Exceptions None
Release
Signature void release()
Description Releases the ESA. This call indicates that the sync core will not use the ESA instance anymore
and the ESA may therefore free all used resources such as connection factories, and shutdown.
This is the last method called on the ESA instance during the sync cycle.
There could be several sync cycles, run on the single ESA instance in sequence. For example,
this happens if needSyncAgain() returns true. Every cycle is ended with an endSync() call.
In contrast, release() is called only once, after the last endSync().
This allows the ESA to keep some resources during all sync cycles, between first startSync()
and release() calls.
Parameters None
Returns Nothing
Description Provides the ESA with a means to call the Sync Core. This is the first method called on the ESA
after the instance is created.
ESA should store a reference to HelperApi in this method.
Rationale The Helper API interface is fully described in ESA HelperAPI Interface. Its major functions are to
provide the actual ESA Parameters values stored inside Agiloft and to help with deletion
tracking. It is also used to start externally triggered synchronizations.
Returns Nothing
Exceptions None
/**
* Sets
HelperApi to be used by ESA, if necessary
* @param
helperApi
*/
public void
setHelperApi(HelperApi helperApi) {
this.helperApi = helperApi;
startSync
Signature String startSync(String externalSystemID)
Description Starts a synchronization. This is the very first method called during Sync.
Rationale Notify ESA that a synchronization is to be started now. The ESA should prepare itself for doing
the synchronization by opening connections to the external system, allocating internal data
structures and so forth.
Parameters externalSystemID - External System ID, as defined in the Sync Configuration wizard.
Returns Sync version, supported by the ESA. This should be one of the SYNC_VERSION_xxx constants.
syncErrorNotify
Signature void syncErrorNotify(String message)
Description Called by Sync to notify the ESA if an error occurs on sync. The exact processing scenario is
not defined and depends on the ESA, which may take actions such as notifying the user or
logging the error.
Rationale Notify ESA that sync ends with an error. The ESA may log this or notify the admin somehow.
Returns Nothing
Exceptions None
Update
Signature Date update(String structure, Date lastSeen, ExternalRecord values)
Rationale Sync subsystem calls this method to propagate changes in an Agiloft record to its External
System peer record.
Parameters
structure - structure to update record in
lastSeen - modification time of the record, as seen by sync last time
values - field and relations of the record to update
Exceptions
EsaRecordException if a record can’t be updated,
EsaRecordDelayedException if the ESA wants to delay the update and run a bulk
update.
OptimisticLockFailureException if record is modified after lastSeen timestamp.
This means that a record was modified while a sync was in progress.
boolean needSyncAgain()
Sometimes, the ESA may decide there is a necessity to re-run synchronization immediately after sync finish. An
example is if update actually updates more than 1 record, so data are changed in a broader scope than the
Sync subsystem expects. In this case, the ESA should return true from this method.
<needSyncAgain>
</needSyncAgain>
<value>boolean value
</value>
<cursor> snippet
<getModifiedPaged>
<structure>
structure-name
</structure>
<after>
timestamp-value
</after>
</getModifiedPaged>
<getCurrentTime>
</getCurrentTime>
<value>timestamp value
</value>
<read>
<structure>
structure-name
</structure>
<id>id-value</id>
</read>
ExternalRecord
snippet
<countRange>
<min-id>
String-value
</min-id>
<max-id>
String-value
</max-id>
</countRange>
<value>integer value
</value>
<getParametersMeta>
<locale>String-value</locale>
</getParametersMeta>
A number of <esa-parameter>
tags.
<getDeleted>
<structure>
structure-name
</structure>
<after>
timestamp-value
</after>
</getDeleted>
<id>id-value</id>
...
<id>id-value</id>
Or
<nullValue>true</nullValue>
<getFieldList>
<structure>
structure-name
</structure>
<locale>
lang-value
</locale>
</getFieldList>
The ESA may decide how to transfer data at runtime depending on the amount. In any case, Agiloft will call
getModified() first and only call getModifiedPaged() if getModified() returns NULL
Or
<nullValue>true</nullValue>
<getRelations>
<structure>
structure-name
</structure>
<locale>
lang-value
</locale>
</getRelations>
Set getStructureList()
Gets a list of external data structure names. Structure is a logical data unit, mappable to an Agiloft table; a
table, a folder, a class in OODB or any other type of data grouping.
<getStructureList>
</getStructureList>
<readDataPage>
<cursor-id>
cursor ID
</cursor-id>
<page-index>
data page index
</page-index>
</readDataPage>
<startSync>
<external-system-id>
String-value
</external-system-id>
</startSync>
<value>1.0</value>
<update>
<structure>
structure-name
</structure>
<value>timestamp-value</value>
<delete>
<structure>
structure-name
</structure>
<last-seen>
timestamp-value
</last-seen>
<id>id-value</id>
</delete>
void endSync()
Returns Bit-ORed constants, restricting synchronization run modes:
1 - Run Manually allowed
2 - Run by Sync Actions allowed
4 - Run by external system request allowed.
<getAllowedRunModes>
</getAllowedRunModes>
<value>
integer value
</value>
<configure>
<external-system-id>
String-value
</external-system-id>
<force>
Boolean-value
</force>
</configure>
<value>
String-value
</value>
<closeCursor>
<cursor-id>
cursor ID
</cursor-id>
</closeCursor>
void leaseSession()
Resets session timeout counter (if the ESA has any).
<leaseSession>
</leaseSession>
<isKnownID>
<external-system-id>
String-value
</external-system-id>
<structure>
structure-name
</structure>
<id>String-value</id>
</isKnownID>
Boolean-value
<getParameter>
<external-system-id>
String-value
</external-system-id>
<name>
String-value
</name>
</getParameter>
<value>
Value. The type depends on the ESA parameter type.
</value>
...
There can be several <value> tags within result
<detectDeleted>
<external-system-id>
String-value
</external-system-id>
<after>
Timestamp-value
</after>
<structure>
structure-name
</structure>
</detectDeleted>
<id>String-value</id>
...
<id>String-value</id>
<enumerateKnownIDs>
<external-system-id>
String-value
</external-system-id>
<known-before>
Timestamp-value
</known-before>
<structure>
structure-name
</structure>
</enumerateKnownIDs>
<id>String-value</id>
...
<id>String-value</id>
<trackRecordDeletion>
<external-system-id>
String-value
</external-system-id>
<time>
Timestamp-value
</time>
<structure>
structure-name
</structure>
</trackRecordDeletion>
Read and familiarize yourself with the Javadoc comments provided with the ESA package. The
ExternalSystemAdapter.java document in com/supportwizard/sync/interfaces/esa lists all of the
ESA methods and explains their semantics and parameters. In addition, you should read and understand the
Sample ESA code.
JavaExecutableEsa
3. Replace the following line: ExternalSystemAdapter esa = new SampleEsa(); with your new class.
a. For a CL ESA, just reassemble the example using make.sh or make.bat scripts. You now have your
own ESA ready to be launched.
b. For an HTTPS ESA, comment the line: EsaRunner.runCommandLineEsa(esa); and uncomment
the line: // EsaRunner.runHttpsEsa(esa, ewServerURL, externalSystemID);.
c. You will need to make further changes, both because you will need to manage the ESA life cycle
yourself, and because you will need to supply the Agiloft server URL and External System ID values.
Delayed Updates
© 2022 Agiloft Inc. 227
Delayed Updates
In some cases, an ESA cannot, or should not, immediately perform CRUD operations. A common case is 'bursting',
when multiple operations can be served within a single request to the external system. Doing every operation in a
single request might be too expensive - it can be ten or even 100 times slower. Another example is updating an
External System in an asynchronous manner.
If the ESA decides to delay an update, it should throw ESaRecordDelayedException from the CRUD method,
supplying a unique structure-scope string token, within a startSync()/endSync() period. This token is later
passed into checkDelayedXXX() methods to get the actual operation result. If an operation failed and an
exception should be thrown, the exception should be thrown from the checkDealyedXXX() method. It is possible
to throw the EsaRecordDelayedException again. The number of update attempts is limited by sync to prevent
infinite loops.
Localization
Some methods have a Locale argument. The ESA follows the Java convention in naming locales: <language-
code>_<country-code>_<variant>. Examples are en_US, ru_RU and Pt_BR. See Localization for more
details.
If the ESA doesn't support the required locale or doesn't support localization at all, it should return an American
English label and hint.
Troubleshooting Tips
As a rule of thumb, whenever you experience unexpected behavior check the esa.log and cl-esa.log. These
reside in the current directory of the Remote Proxy, that is, the directory where you run the java -jar
esa.jar command. The esa.log file will hold the remote proxy logging, including the XML message dumps,
whereas cl-esa.log will only contain logging from your ESA. Most of the time these logs will contain relevant
information about the events. ESA logs are very detailed and contain full XML message dumps, as well as
debug information.
Save your configuration before connecting an ESA to the server. Note that it is auto-saved when you click
Next on the General tab. If not, you may see the following exception message:
anatoly@anatoly
syncHome> java -jar esa.jar
Agiloft ESA Remote Proxy running.
Connecting to http://anatoly:8080 (External System ID is 123) to get sync
parameters...
IO Error (see esa.log for more details):Can't connect to
This error commonly appears when running the ESA before leaving the Sync Configuration wizard.
Check that the Remote Proxy connects to the correct server and port, and has the right External System ID.
If not, please use the full syntax for the connection: java -jar esa.jar -ewhost anatoly:8080 -id
ewhost denotes the server name:port, and ID standards for the externalID in the Sync Configuration wizard,
at the bottom of the General tab.
Check that the command line cmd/c xxxxx\run.bat is correct.
Never use System.out.println for debugging. You will never see the message because it will be hidden bet
ween RemoteProxy java -jar esa.jar and your ESA run.bat processes. Instead, use log traces.
Unit test your code. it is difficult to debug CL ESA applications, because the process is automatically started
by the RemoteProxy app. It is much easier to debug unit tests.
Check that your ESA follows the Javadoc standards from the ExternalSystemAdapter interface. In particular,
check the return values for create() and update() - these should never return null.
If you still have problems, please contact Agiloft support and submit a bug report. Please provide the exact steps to
reproduce the problem and all the necessary credentials and code.
Deletions
For many systems, tracking deleted records is problematic. The Agiloft HelperAPI can simplify this task. If you can't
easily track deletions in the external system you can either:
Enumerate all record IDs known by Agiloft and check whether those records actually exist at the moment, or
Report deletion events to Agiloft when deletion happens in the external system. The latter is usable if you
can somehow obtain deletion notifications from the external system.
Transactions
Sync is transaction-less, because not all external systems support transactions or expose them over integration
APIs. If an external system requires transaction usage, the ESA should behave as it would with auto-commit on in
SQL, meaning it should execute every operation in a separate transaction.
Collections
Collection fields may hold complex objects, which can be mapped to Agiloft tables. For this reason, getFields,
getRelations and getCollections calls should accept both structure names and collection IDs.
Note: An ESA must ensure that structure names and any collection IDs use different name spaces.
Currently, delayed updates due to locks are not supported, but this feature is likely to be added in the upcoming
releases because it may speed up syncn appreciably. It is a good idea to make your ESA ready to throw some
exception when a pessimistic lock is detected - this might be employed in the next Sync release.
When a record update/create/delete error occurs, the ESA should respond with EsaRecordException. Such
exceptions indicate a single record related error, probably recoverable in the future and do not stop the syncn cycle.
On the next cycle, sync will try to re-synchronize the erroneous records.
Cursors
© 2022 Agiloft Inc. 230
Cursors
In some setups, getModified/getDeleted may return a huge amount of data. It may be inefficient or even
unfeasible to transfer and process such traffic in one call.
To overcome this, Data Cursors can be employed. A Data Cursor is a paginated stream of data. When requested,
cursor "core" information such as ID and page number is transferred from the ESA to Agiloft. Agiloft requests
actual pages data from the cursor passing readDataPage messages supplying cursor ID and page index.
Cursors are a more advanced technique than the simple return of all data. Their support is optional in the ESA. For
the getModified function, Agiloft first sends getModified, which has a chance to return all data at once. If and
only if the getModified response is a USE_PAGES string and not an empty result, Agiloft sends
getModifiedPaged, which returns a cursor. The ESA may choose the transferring approach in runtime,
depending on the amount of the data.
When a data cursor is fully read, the cursor is closed. However, due to possible communication failures, this may
not happen. The ESA should retire cursors in 15 minutes since the last readDataPage or leaseCursor message.
ESA Parameters
The ESA is likely to have configuration parameters, such as external system host, login, password, etc. While the
ESA may maintain them by its own configuration mechanisms, it is often desirable to have them in one place, to
simplify administration and maintenance.
For this purpose, Sync may store some ESA parameters within the Agiloft database, providing a simple GUI for
changing them. To do this, Sync asks ESA for the names and types of parameters to manage. The ESA provides
parameter meta-data such as type, default value, required / not required, and Sync shows them in the configuration
GUI, ESA Settings page.
ESA may later request its parameter values during the syncn cycle, using parameter names. See getParameter
message. Though the number of parameter types is limited they are likely to cover most of the ESA configuration
needs and simplify administration greatly. If, for example, your ESA will require some configuration file, it is a good
idea to put the local path to it in an ESA parameter.
Parameter names starting with config are reserved for getting Sync Configuration settings. Currently, the following
are available:
It is likely that only config.pollperiod is of interest for ESA developers, and only for HTTPs ESAs.
Timestamps
© 2022 Agiloft Inc. 231
Timestamps
The time resolution for sync is one second, and all timestamps are truncated to the closest second. All timestamps
passed to or received from the ESA are in UTC - GMT, Greenwich time. It is the responsibility of the ESA to
translate external system timestamps into UTC, taking time zones and daylight savings into effect.
Sync may compute the system clock difference between the external system (ESA) machine and the Agiloft host
machine. To do so, sync passes a getCurrentTime message to the ESA. If the ESA wants the clock difference to
be taken into account, it should respond with the current external system UTC time. Alternatively, the ESA may
respond with an empty result, setting clock difference to zero.
If you are sure that the Agiloft host and external system host have their system clocks synchronized, for example
via Network Time Protocol, NTP, it is a good idea to turn clock difference computation off by responding with an
empty message. Since the Sync time resolution is less precise than NTP, it will never get better results than it could
if the system clocks are really synchronized.
Due to limited time difference precision, it is possible that some records may stay unsynchronized. Though the
chance of this is very low, it is always better to have machine clocks synchronized and time difference detection off.
If you are unsure that clocks are synchronized, for example, your ESA can be deployed on a number of machines,
you should run getCurrentTime. The time difference calculation works best if the ESA actually measures time in
the middle of a message delivery + message processing + message delivery back loop, but in practice, it is fine to
just measure and respond as fast as you can.
The Time difference calculation is performed on each Sync cycle, so if system clocks are corrected between
synchronizations, Sync will work correctly. It is however, presumed that system clocks are not changed during
synchronization.
Last seen timestamps are always passed just as they were received from the ESA last time and thus are not
affected by system clock changes.
startSync
Signature
String startSync (String externalSystemID, boolean waitForCompletion)
String startSync (String externalSystemID)
Description Starts synchronization, whose settings are in the synchronization configuration, identified by
External System ID.
If you use this method in HTTPs ESA, be sure that HelperApi uses HttpXmlEsaTransport.
Parameters
externalSystemID - External System ID, used to find sync configuration
waitForCompletion (optional) - indicates whether control should be returned
immediately, or when synchronization finishes.
Returns One of SYNC_RUN_ constants. If ended with a SYNC_RUN_ERROR, an error message may
follow, separated by space.
Exceptions None
Description Gets an ESA parameter value, stored on the Agiloft server. The value is a list, which may
contain any number of real values, depending on the parameter type.
Purpose ESA uses this method to obtain an ESA parameter value. The whole chain of events looks like
this:
1. Sync core queries the ESA for the list of parameters it wants the sync core to manage,
using the ExternalSystemAdapter.getParametersMeta() call.
2. Agiloft generates a Sync Configuration / ESA parameters screen.
3. The Agiloft admin enters the parameter values in the screen.
4. Agiloft saves the values into its database.
5. At a later time, typically from startSync(), the ESA calls HelperApi.GetParameter to
get the stored values.
Parameters
externalSystemID - External System ID, used to find sync configuration
name - Name of ESA parameter, as returned from getParametersMeta()
Exceptions None
getPollingPeriod
Signature Integer getPollingPeriod(String externalSystemID)
Description Provides the Polling Period parameter as set in the General tab of the Sync Configuration
wizard.
When an HTTPs ESA finishes a synchronization, or receives a timeout when getting an XML
message, it should reconnect to the Sync subsystem core to make itself available for the next
synchronization.
The polling period defines the time period to wait before reconnect, as set by the Agiloft admin.
Parameters externalSystemID - External System ID, used to find the sync configuration.
Exceptions None
Other methods of HelperApi are devoted to tracking deletions. For many systems, this is the most difficult part of
creating an ESA because records are simply deleted without a record that they existed. This makes deletion
tracking somewhat difficult to implement. In some cases though, the following Helper API can help:
trackRecordDeletion
Signature void trackRecordDeletion(String externalSystemID, String externalStructure, Date time, String
id)
Description Records a record deletion into an internal log, associated with this externalSystemID.
Purpose If an ESA, such as HTTPs ESA, receives deletion notifications from the external system, it may
forward them to the sync core immediately without the need to maintain an internal list of
deleted records.
This method does not imply any limitations on the nature of the ID, but requires that ESA
receives deletion notifications. Usually this means that the ESA, or a part of it, must always be
running.
Parameters
externalSystemID - External System ID, used to find sync configuration
externalStructure - Structure to which the record belonged
time - Time when a record was deleted
id - ID of the deleted record
Returns Nothing
Exceptions None
When it is not possible to subscribe to deletion notifications, it might be possible to find out which records were
deleted by comparing a list of all external records ever reported to the Agiloft sync core, that is never synchronized,
and the list of records currently existing in the external system.
detectDeleted
Signature Set<String>detectDeleted(ExternalSystemAdapter esa, String externalSystemID, String
externalStructure, Date after)
Description Detects which records were deleted, using countRange() and binary division approach
Purpose If the ID is monotonic and never reused, for example an auto-increment DB key, it is possible to
find deleted records by finding gaps in the ID sequence.
Parameters
esa - ESA instance, used to call countRange()
externalSystemID - External System ID, used to find sync configuration
externalStructure - structure which the record belongs to
after - date as passed to the getDeleted() method
Exceptions None
isKnownID
Signature Boolean isKnownID(String externalSystemID, String externalStructure, String id)
Description Checks whether a record with this ID was ever reported to the sync core, in other words, if it
was ever synchronized.
Purpose If the ID nature does not allow using the detectDeleted() method but the ESA can apply another
fast algorithm for comparing known and actual lists, the ESA may check whether the sync core
is aware of the record using this method.
Parameters
externalSystemID - External System ID, used to find the sync configuration
externalStructure - Structure which the record belongs to
id - ID of the deleted record
Exceptions None
enumerateKnownIDs
Signature Boolean isKnownID(String externalSystemID, String externalStructure, String id)
Description Returns a list of records known to sync core, in other words, the records that were ever
synchronized.
Parameters
externalSystemID - External System ID, used to find sync configuration
externalStructure - structure which the record belongs to
knownBefore - date as passed to the getDeleted() method
Exceptions None
While built for illustration purposes, the sample ESA is a fully functional ESA that syncs with a virtual external
system which is either co-located or running on an external server.
Note that the Sample Remote Proxy code simulates running on a remote server that contains the remote
external system adapter and database. This is why this documentation refers to the external system and its
data tables as 'virtual' – there is no actual remote external system database and no actual remote external
system data tables. Those 'tables' and 'data' are pre-loaded in the Sample ESA bundle to make this
process easier to set up and understand.
To demonstrate how the ESA works, the virtual external system includes two tables: 'Virtual Contacts', with the
fields ID, Full Name, and Email; and 'Virtual Cases', with the fields ID and Summary. The AgiloftEmployees table
will be mapped to Virtual Contacts and the Agiloft Support Case table will be mapped to Virtual Cases. Actual data
is stored in a file directory, using a Java properties format - plain for Employees/Contacts, XML for Cases.
Employees/Contacts files use the naming convention contact_123, where 123 is the record’s value in the ID field.
Similarly, Case files use the naming convention case_123.
The Sample ESA uses a single parameter, Work Directory (workDir), to hold the directory path for storing data files.
The value of the Work Directory parameter is input on the ESA Settings screen which appears once a connection
gets established, as per Step 11 below. Note that this directory will be a subdirectory of the initial sync Home
directory defined in Step 1 of the ESA builds.
Note that the synchronization process is nearly identical for both Windows and Unix systems, with only the directory
paths and therefore command line text being unique to each system. However, in the interest of clarity this manual
will show the Windows installation process.
1. Create a work directory named 'syncHome' on your local directory, for instance C:\syncHome. This will be
the directory where data and files are stored.
2. Note: the actual name of the directory is not important, but the rest of this guide will refer to the work
directory as syncHome.
1. In your KB, go to Setup > Sync > New > External Sync to open the Sync Configuration wizard.
2. Add the following details:
a. Configuration Name - add a descriptive name such as MySync
b. external system Type - select Third-party Adapter from the drop-down
c. Select the Third-party ESA (Command Line) radio button
d. Select 1 for Command Line Parameters
e. Status - Enabled
f. Directions - Two-way sync
g. Conflicts - external system should take precedence
h. Remoting - select the external system Adapter runs remotely radio button. This will enable the
Download ESA Remote Proxy button.
i. The external system ID Prefix is system-generated and will be used by sync to connect to the ESA
Remote Proxy.
3. At this point, leave the window open while you complete the next sections of steps to set up the ESA
development package. Only when the package is ready should you click Next and proceed.
1. In the external system Type section of the Sync Configuration wizard, there is a link named "To download the
ESA developer bundle, click here." Click the link to download the sync-example.zip file.
The syncHome directory should now contain the following files and folders, which can be verified using the
Command Window. Type 'dir' to view the contents of the directory, or 'dir/p' to show one page on the screen at a
time:
com (directory) Contains example sources and a Java support library. The sample ESA code is in
the com.supportwizard.sync.sampleesa.SampleEsa class, located in the
SampleEsa.java file in the com/supportwizard/sync/sampleesa directory.
Start_ESA.bat This file needs to be in either the C:\Agiloft folder or its location in the user's PATH
environment variable. It is invoked by the sync subsystem to make the connection
with the ESA Remote Proxy in Windows.
Start_ESA.sh This file needs to be in either the /usr/local/Agiloft folder or its location in the
user's PATH environment variable. It is invoked by the sync subsystem to make
the connection with the ESA Remote Proxy in Linux.
1. In the Command Prompt window, in the syncHome directory, type make.bat and click Enter. This is a script
that executes several commands that are necessary for the sample ESA to function correctly.
2. Once the script has run, keep the command prompt window open and return to your browser.
1. The sync subsystem will call the Start_ESA.bat script to execute run.bat to launch the Agiloft ESA.
First, make sure that Start_ESA.bat matches your configuration.
2. Note that the Start_ESA.bat file should be in the syncHome directory, and the location added to the user's
PATH environment variable.
3. Start_ESA.bat expects that in Step 3 above you specified 1 for the Command Line Parameters number in
the external system Type section. If you chose a number other than 1, you must edit Start_ESA.bat and
change the PARAM value to match. If your ESA home directory is not C:\syncHome you must also modify
the path of the run.bat file:
set
/A PARAM = 1 - or = number of CL Parameters specified during configuration
IF
%1==%PARAM% cmd /c C:\syncHome\run.bat - or C:\your Sync
directory\run.bat
Varying the number of command line parameters will allow you to run multiple remote ESAs.
1. At this point you should have walked through all previous steps and added all of the settings, with the Sync
Configuration wizard open in the browser. In the Sync Configuration wizard, click Next. This launches
the Agiloft sync subsystem and begin polling, where it will be waiting for a connection from the external
system Remote Proxy ESA.
2. While the system is trying to connect, in the Command Window enter java -jar esa.jar and press
Enter. This command tells the ESA Remote Proxy to connect to the Agiloft sync subsystem, obtain the
command to launch the sample ESA, and run it. You should see a success message similar to the following:
3. When you return to the browser window, the ESA Settings screen will have appeared, where you can set the
directory to store the synchronization data files.
1. In the syncHome directory, create a sub-directory named sampledata. This will be where data files are
stored following synchronization.
2. In the Work Directory field of the ESA Settings tab, enter sampledata.
3. Click Next to open the Mapping screen.
1. The Mapping screen contains a list of the tables in the KB, which can be mapped to structures in the external
system. Here you can establish the relationships between Agiloft KB tables on the left, and external system
ESA tables in the drop-down list on the right. Note that the external system tables are 'virtual' in this
example, since there is no real external system or tables.
2. Begin by selecting Virtual Contacts from the Employee drop-down to map the Employees table to the Virtual
Contacts external system table, then click Map to open the Field Mapping screen.
3. In the Field Mapping screen, you can define the mappings between fields in your Agiloft table, and in the
selected external system table. In this example, create the following mappings between Agiloft and the
external system respectively:
1. The Relation Mapping screen enables you to map external relations to linked field sets in Agiloft. In this
case, the Customer relation will be mapped to the Employee(Cell Phone, User Company, Customer
Phone...) linked field set. This will reflect the relationship between Support Case-Virtual Cases and
Employee-Virtual Contacts.
2. Click Next to navigate to the Running screen.
3. Select Manual to specify the synchronization process initiation mode.
4. Click Finish.
5. The sample Remote Proxy ESA sync subsystem is configured.
Running Synchronization
This section describes how to perform synchronization between your sample ESA and your Agiloft KB.
If you need to re-sync using a clean synchronization, don't just delete files and re-run
synchronization. Doing so may result in propagation of deleted files, if this is permitted in the sync
configuration. To reset a full match history, edit your sync record and click the Resets Records
Peering button at the bottom of the Sync Configuration wizard.
4. Clicking the affected records link opens the Synchronization Results screen where you can view all of the
records which were created, updated or deleted between Agiloft and the external system.
The Javadoc comments for the classes and methods of the ESA are in the ExternalSystemAdapter.java document
at syncHome\com\supportwizard\sync\interfaces\esa.
SampleEsa
package com.supportwizard.sync.sampleesa;
import com.supportwizard.sync.interfaces.esa.*;
...
...
If you plan to use the Java support libraries, your ESA class should implement the ExternalSystemAdapter
interface. This single interface contains all of the ESA operations. Some of them are only called under certain
circumstances. To have the full implementation of the auxiliary methods, extend the ExternalSystemAdapter base
class.
Logging
/**
* Sample ESA uses log4j logger. You may use any other logger, such as java.
util.logging.Logger,
* but DO NOT USE System.out for logging.
*
* Command-line ESA uses System.in and System.out to exchange XML messages with
sync core.
* All non-XML output is omitted, so you will never see it.
*/
private Logger log = Logger.getLogger(SampleEsa.class);
The Sample ESA works as a command line (CL) ESA. This means that its standard input and output can be used
for exchanging XML messages.
Do not print anything on the standard output. Use the logging facilities instead. By default, all the Sample ESA
logging goes into the cl-esa.log file, and the Remote Proxy logging goes into the esa.log file, both in the home
of the syncHome directory.
Work directory
The following method returns the parameter metadata described for the sync core:
Metadata
public
List<EsaParameterMeta>getParametersMeta(Locale
locale) throws EsaException, RemoteException
startSync()
The List<ESAParameter> paramValues.... WORK_DIR); line queries the sync core callback interface for
the stored parameter value, passing the parameter name workDir as a key.
Sync State
The startSync() method sets the ESA into a 'synchronization is running' state.
Starting synchronization
endState()
The Sample ESA constructor does a one-time initialization which only sets up the logging. A single instance can be
used to run multiple synchronization cycles, so most of the initialization should probably be done by the
startSync() method.
The ESA parameter values should not be queried in the constructor. The sync core callback interface has not yet
been set. Also, it is possible that the ESA parameter is not yet managed because getParametersMeta was not
called. Therefore, the parameter value query should only be done in the sync state.
Metadata
The ESA returns information about external structures/tables, fields in each table, and relations between them,
using the following code:
public Set<ExternalField>getFieldList(String
structureOrCollection, Locale locale)
...
public Set<ExternalRelation>getRelations(String
structureOrCollection, Locale locale)
Note: the term structure is used to highlight the fact that, physically, this could be almost anything: a database table,
a file, an object-oriented database, and so on.
...
Note: structure is a structure logical name, as returned from the getStructureList() method. In addition, if the
after timestamp in getModified is null, this means 'return all records'. Usually it has a null value on the initial
synchronization, or after a Reset Records Peering is executed.
Data Updates
External records are manipulated through these methods:
...
Exceptions
Exceptions are reported using a <result> tag with a nested <exception> element.
Exceptions of type optlockfailed and concurrentdelete should also have a nested tag:
Exceptions of type optlockfailed and concurrentdelete should also have nested tags:
<external-id>record ID</external-id>
<modified-at>record ID</modified-at>
These should contain exception stack trace or some other diagnostic information (FILE/LINE, etc) which would
allow ESA developer to investigate the problem better.
All 'legal' ESA exceptions are derived from the EsaException class.
The generic EsaException is fatal and aborts the synchronization.
EsaRecordException, in contrast, does not abort the whole synchronization and just marks a single
record as failed.
OptimisticLockFailureException is used to implement optimistic locking for the time between when a
record is read, using getModified or read, and the time it is updated. If it fails, the synchronization cycle
will repeat.
All unexpected exceptions, other than EsaException, abort the synchronization.
HTTPS
Command-line (CL) standard input/output (STDIO)
In both cases, the same XML messages are passed back and forth, and the ESA operation semantics are identical.
However, the ESA implementations themselves could be quite different.
Note: The ESA Remote Proxy bundled with the Agiloft application converts the command-line ESA into an HTTPS
ESA, thus allowing the command-line ESA to run remotely over HTTPS.
Both the CL and HTTPS approaches are quite versatile and both allow for implementations of the ESA in any
language using any technique. There are two implementation styles because external systems can be very different
and, depending on particulars, one ESA might be easier to implement than the other. For example, if your system is
a Windows application and you need to call its Common Object Model (COM) interfaces, a CL ESA would be easier
to implement. Since the ESA application is automatically launched by Agiloft when needed, you don't need to write
a continually-running Agiloft polling service. The application is quite straightforward in that case: receive the call
from the Agiloft sync core, and then do the necessary calls on the COM interfaces. By contrast, if your system is of
a client-server type, such as a J2EE server, it might be more natural to implement an HTTPS ESA. This could run
on the server side, where you can directly access your back end.
XML message syntax and semantics are the same for both CL and HTTPS ESA types.
Life cycle is not under the control of Automatically launched when needed
the Agiloft
Actively polls for messages Just reads messages from its standard input
Usually an internal part of your A separate executable which usually connects to your system
system
Often remote Needs a special Remote Proxy application that is shipped with Agiloft in
order to be remote
If you plan on implementing an ESA using Java, you can make use of the support library bundled with the ESA
example which handles all communication details like parsing, generating XML, exchanging XML messages with
the Agiloft sync subsystem and so on. All you need to do is provide a single Java interface
ExternalSystemAdapter and modify the main JavaExecutableEsa class provided in the example. This will
result in a ready-to-go CL ESA application. If you are implementing an HTTPS ESA, see Java ESA Development for
more information on the methods that will be called.
URL Action
connid=<connection id>
message=XML message
connect
get-post
get-post...
get-post
disconnect
HTTPS ESAs are usually remote, though this is not a requirement, and their lifecycles are fully independent of
the Agiloft server. They are just considered to be running externally and to be polling the Agiloft sync subsystem
for new messages at prescribed intervals. If a synchronization is started, it will stay pending until the HTTPS ESA
connects and receives sync messages.
If you write server-side software, an HTTPS ESA would generally be the best option. It uses a blocking poll
approach, which means that GET requests are blocked on the Agiloft server until a message is ready for the ESA
or a timeout occurs.
Because these parameters are required before connecting to the Agiloft sync subsystem, the ESA parameters
mechanism is not applicable and the parameters should be provided beforehand for the ESA separately. Usually,
they are identifiable within the settings of your system.
URLs
There are 3 URLs used for HTTP transport. If the Agiloft GUI runs at https://www.example.com/gui2, these URLs
will be: https://www.example.com/gui2/sync/connect, https://www.example.com/gui2/sync/disconnect, https://www.
example.com/gui2/sync/message.
Every request to any URL must contain an extsysid=<externalSystemID> parameter, where <externalSystemID>
matches the external system ID field in the Sync Configuration.
Important: A third-party ESA, using HTTP transport must be aware of its externalSystemID.
HTTP response codes should always be examined to check whether transportation was successful. All parameters
– GET and POST – should always be URL-encoded. Character encoding is UTF-8.
Connect and disconnect URLs should be accessed when the ESA is about to start/stop receiving messages. These
requests don't have any other parameters. The HTTP response to connect will be an externalSystemID value
passed in the HTTP request. The ESA may check this in addition to the HTTP response code to ensure it has
connected successfully.
The message URL can be accessed via a GET HTTP request to get the next message or via a POST HTTPs
request to send a message. When GETting message, the XML message is returned as a HTTPs response. When
POSTing, the message should be passed as the message HTTPs form parameter.
Cycle
© 2022 Agiloft Inc. 252
Cycle
The typical ESA cycle for using HTTPs transport is:
GET /sync/connect
GET /sync/message
Process the message.
POST /sync/message
If the message was not Release, goto 2
GET /sync/disconnect
Wait for some period of time. The Sync Configuration setting Polling Period is accessible as the config.
pollperiod parameter which you may request from within sync cycle 1-6. Goto 1
HTTPs transport does not limit the way the ESA is actually implemented. It can be a command-line application, a
service, a component within an application server, etc.
Starting and stopping this application and the exact mechanism by which the ESA obtains the external system ID
value are outside the scope of this spec, but typically the application is installed just like any other piece of software
and reads the ID from a command line parameter or configuration file.
This type of third-party ESA uses standard input and output streams, stdio. In this case, the ESA developer
provides his ESA as an executable, command-line application. It need not be a compiled native application; it could
even be a script. The two requirements are:
A CL ESA is often easier to write because it doesn't require an always-running server. A CL ESA uses a standard
IO (STDIO) interface to exchange XML messages with the Agiloft sync subsystem. XML messages from sync are
written to the ESA application Standard Input (STDIN) and responses are read from the application's Standard
Output (STDOUT).
The following example demonstrates how a manual CL ESA could look, including the console text and the sample
ESA application response:
//CL console
anatoly@anatoly my-esa>./Start_ESA
<sync>
//CL console
<sync>
<esa-call call-id="2">
<release/>
</esa-call>
</sync>
//CL console
anatoly@anatoly my-esa>
There is a parser and a serializer provided with the Java support library, so you should only need the raw scheme if
you are developing an ESA without them.
Line breaks
Although the XML standard says little about line breaks, standard console I/O is line-based. To handle this
difference, every <sync> and </sync> tag which delimit all XML syncn messages must be placed on a line of its
own.
Cycle
© 2022 Agiloft Inc. 254
Cycle
The typical ESA cycle using STDIO transport is
For ESA's using STDIO, Sync is responsible for starting the ESA application when necessary.
Remote Proxy
A CL ESA is usually easier to write, but it is often desirable to run an ESA remotely. Since this is not directly
possible with CL applications, Agiloft supplies a small application called the Remote Proxy which converts a CL
ESA into an HTTPS ESA. The Remote Proxy is a Java Jar file which can be downloaded by clicking the Download
ESA Remote Proxy button from the Sync Configuration screen. The downloaded esa.jar file will contain both the
Remote Proxy code and a settings file which references the sync configuration, namely the Agiloft server name
and external system ID of the configuration which were created via the sync GUI. It should then be run in standard
Java on the machine of your choice to start polling the Agiloft sync subsystem.
1. Download and install the latest Java version, e.g. JRE 1.6, from www.java.com to the external system host, if
it isn't already installed.
2. Fill in the command line field in the Sync configuration GUI.
3. Download "esa.jar" by clicking the "Download ESA Remote Proxy" button in the Sync Configuration wizard.
4. Start the ESA Remote Proxy application by running the "java -jar esa.jar" command on the external system
host.
ESA Remote Proxy doesn't change the current directory. If you need the current directory to be different from the
one containing the esa.jar file, use a path to it within the start up command:
For example:
When you download esa.jar from the Sync Configuration GUI, it will have the necessary configuration parameters
such as ESA type, External System ID and polling period predefined within the jar file. Those parameters can also
be changed using command-line arguments in the java -jar command:
-ewhost Host and port where Agiloft is run. This parameter is useful if a server "outer" name
differs from its "intranet" name
The Remote Proxy should normally always be running on the external machine similar to a daemon. The Remote
Proxy will launch and stop a CL ESA at the request of the sync subsystem.
Notes
The Java archive file is named esa.jar because it also contains some inbuilt ESAs shipped with
the sync subsystem, and is used in exactly the same way and does not contain any of your code.
Normally it will connect to the Agiloft server it was downloaded from and start polling it for messages:
In the External Sync setup, you must click Next to save the sync configuration before running the
remote proxy.
You may specify an IP address from which the system can only receive connections. If you are using
NAT, you should specify the external IP address as "seen" by Agiloft, rather than the internal
address.
When it receives messages, it will run the CL ESA and pass the messages to it. The following illustration depicts a
configuration of the Agiloft sync server, a Remote Proxy and a CL ESA. The Remote Proxy and the CL ESA reside
in the external machine.
For maximum security, it is strongly advised that you run your Remote Proxy under a separate account, which is
only allowed to run your CL ESA application and does not have permission to do anything else. UNIX users may
use a sticky bit to give more rights to the CL application.
Method Description
Runs command line ESA. This method starts by reading standard input,
public static void converts received XML messages into ESA calls, dispatches them and
runCommandLineEsa prints the XML-ized responses to standard output.
Usually, you will not need to use any other parts of the Java support library directly. However, if you plan to
implement an ESA using a programming language other than Java, or plan to change the way XML messages are
passed to and from or the way they are processed, such as by implementing some kind of internal queuing, you
may find this useful to explore.
Class Description
with
Transport: These classes are responsible for the XML message flow.
These classes translate ESA and HelperApi calls into XML messages.
EsaTransmitter
HelperApiTransmitter
EsaReceiver This class takes messages from Transport and invokes ESA methods according to
the message received.
The library classes are supplied in the source form. You are free to reuse them for any Agiloft ESA-related
development.
Reusable Classes
The Java ESA Example following contains a number or classes, which can be reused by any Java ESA. These
classes are:
ExternalSystemAdapter interface. Your ESA implementation must include a class, implementing this
interface. In fact, this is the only piece of code you must write to get a fully working ESA, based on the
example code.
Message classes, residing in com.supportwizard.sync.interfaces.transport package. These classes
encapsulate XML messages to be exchanged.
The only things needed to build an ESA using STDIO transport are:
You may reuse any classes of the example and handle messages in some other manner, if you choose, but we
recommend the method above to facilitate incorporating future changes and fixes.
"Plain" properties:
#comment
fieldA=value A
fieldB=value B
"XML" properties:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>comment</comment>
The particular type is to be defined by the ESA Parameter format. The set of fields is defined by the ESA Parameter
fields.
Principal Classes
Two classes are central to this example: FileDirectoryEsa and JavaExecutableEsa. Other classes in the example
can be treated as a Java support library. You don't have to modify them and may use them as is in your own ESA.
1. Uncomment and correct the first line of the run.bat file to read @cd $ESA_DIR$, where $ESA_DIR$ is the
directory where the example is unzipped.
2. Select Third-party command-line in the Sync Configuration Wizard.
3.
© 2022 Agiloft Inc. 262
3. In the same GUI, put the command cmd /c $ESA_DIR$\run.bat in the input box labelled Command Line.
4. Set up the rest of the configuration.
package com.supportwizard.sync.interfaces.esa;
import com.supportwizard.sync.interfaces.esa.ExternalRecord;
import com.supportwizard.sync.interfaces.esa.Cursor;
import com.supportwizard.sync.interfaces.esa.ExternalRelation;
import java.util.Set;
import java.util.List;
import java.util.Locale;
import java.util.Date;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* An interface of External system connector.
* Every adapter (Data provider) should implement it.
*
* This interface has "throws RemoteException" on each method, in order to be
* {@link java.rmi.Remote}-compliant. An implementation may or may need use this
perk.
* Such an exception, if thrown, is considered a serious, unrecoverable
synchronization error,
* like a communication link failure or such.
*
* Some methods do have "Locale" argument. It follows Java convention in naming
locales
* (<language-code>-<country-code>-<variant>).
* Examples are "en_US", "ru_RU" and "pt_BR".
* See http://java.sun.com/j2se/1.4.2/docs/api/java/util/Locale.html for more
details.
*
* If ESA doesn't support required locale or doesn't support localization at all,
* it should return (American) English label and hint.
*
*
* @author <a href="mailto:[email protected]">Anatoly Kochubey</a>
* @version $Id: ExternalSystemAdapter.java.txt 140548 2013-09-13 11:42:12Z
alexg $
*/
/**
* Starts a synchronization. This is the very first method, called during
Sync.
*
* @param externalSystemID should be associated with a current session (if
applicable).
* It is also for simple ESA, which might need to call <ew:shortproductname>
<span class="product1">EW</span><span class="product2">SaaSWizard</span></ew:
shortproductname> back, but don't do this outside
* of synchronization cycle - they may avoid storing externalSystemID
because of this.
* @return Sync version, supported by ESA. This should be one of
SYNC_VERSION_... constants
*/
String startSync(String externalSystemID) throws EsaException,
RemoteException;
/**
* Resets session timeout counter (if ESA has some). Non-WS ESAs may simply
ignore this.
*/
void leaseSession() throws EsaException, RemoteException;
/**
* Finishes synchronization session. This call denotes successful end of a
Sync.
* ESA may clean up any resources, close connections, etc.
*/
void endSync() throws EsaException, RemoteException;
/**
* Releases ESA (it may free all the resources).
* This is the last method, called on the ESA interface.
*/
void release() throws EsaException, RemoteException;
/**
* Returns Bit-ORed constants from {@link RunModes}, restricting
synchronization run modes.
* For example, ({@link RunModes#RUN_SYNC_ACTIONS} bit-OR {@link
RunModes#RUN_EXTERNAL_TRIGGERED})
* would designate that ESA supports non-interactive synchronizations by
both <ew:shortproductname> <span class="product1">EW</span><span class="product2"
>SaaSWizard</span></ew:shortproductname> and External System requests
* @return allowed operation modes, bit-ORed constants from {@link RunModes}
/**
* Sometimes, ESA may decide there is a necessity to immediately re-run
synchronization after
* sync finish. An example is if update actually updates more than 1 record,
so data are changed in
* a more broader scope, than sync subsystem expects. In this case, ESA
should return true from this method.
* The method is typically invoked before {@link #endSync()}.
* @return true if ESA wants another synchronization round to be run
immediately.
*/
boolean needSyncAgain() throws RemoteException, EsaException;
/**
* Gets current UTC time of a remote system, if available.
* Sync uses this method to compute time shift between <ew:shortproductname>
<span class="product1">EW</span><span class="product2">SaaSWizard</span></ew:
shortproductname> server and External System.
* The shift is taken into account for all datetime "control" values, passed
to ESA (getModified(),
* record.modifiedAt, lastSeen timestamps, etc).
*
* <p>Note: This shift is not applied when converting record date/datetime
/time fields.
*
* @return current UTC time of a remote system, if available, or null.
* @throws EsaException
* @throws RemoteException
*/
Date getCurrentUTCTime() throws EsaException, RemoteException;
/**
* Configures ESA to use with an <ew:shortproductname> <span class="product1"
>EW</span><span class="product2">SaaSWizard</span></ew:shortproductname>
instance.
* This method is called on the ESA when Sync configuration is created
* (this also lets to check up ESA availability).
*
* The implementation of this method depends on the ESA.
*
* Simple ESAs, which doesn't need to communicate (call) <ew:
shortproductname> <span class="product1">EW</span><span class="product2"
>SaaSWizard</span></ew:shortproductname> back (i.e. neither use Helper API
* nor initiates Sync by External System requests) may simply ignore this
call
* and return the passed externalSystemID.
*
* More complex ESAs should store (persist) those values somewhere and use
/**
* Gets a list of external data structure names.
* "Structure" is a logical data unit, mappable to <ew:shortproductname>
<span class="product1">EW</span><span class="product2">SaaSWizard</span></ew:
shortproductname> table; a table, a folder,
* a class (in OODB) or any other type of a data grouping.
*
* @return list of external system structures
* @param locale
*/
Set<ExternalStructure> getStructureList(Locale locale) throws EsaException,
RemoteException;
/**
* Gets a list of fields, for a given structure or collection.
* ID and "ModifiedAt" fields might be not included.
* For a collection, the method should return all mappable fields of a
collection members.
*
* @param locale
* @return list of fields
*/
Set<ExternalField> getFieldList(String structureOrCollection, Locale locale)
throws EsaException, RemoteException;
/**
* Gets a list of relations (links) from the given structure.
* This should only include relations, "navigatable" from this object.
*
* @param locale locale
* @param structure structure or collection to query relations for
* @return relation list
*/
Set<ExternalRelation> getRelations(String structureOrCollection, Locale
/**
* Gets a list of relations (links) from the given structure.
* This should only include relations, "navigatable" from this object.
*
* @param locale locale
* @param structure structure or collection to query relations for
* @return collections list
*/
Set<ExternalCollection> getCollections(String structureOrCollection, Locale
locale) throws EsaException, RemoteException;
/**
* Gets localized ESA Parameters meta-data.
*
* @param locale locale
* @return list of ESA private parameters
*/
List<EsaParameterMeta> getParametersMeta(Locale locale) throws EsaException,
RemoteException;
/**
* Gets all records, modified in external system after given timestamp.
* For some systems, this may result in a great amount of data to be
transferred,
* especially for the initial sync. If the ESA predicts such a traffic, it
should
* return null from this method (NOT an empty set!).
* In this case, <ew:shortproductname> <span class="product1">EW</span><span
class="product2">SaaSWizard</span></ew:shortproductname> will make use of {@link
#getModifiedPaged} method.
*
* ESA may decide how to transfer data in runtime (depending on their
amount).
* In any case, <ew:shortproductname> <span class="product1">EW</span><span
class="product2">SaaSWizard</span></ew:shortproductname> will call {@link
#getModified} first and only call {@link #getModifiedPaged}
* if {@link #getModified} returns null
*
* @param structure structure to read
* @param after timestamp
* @return records, modified after given timestamp.
*/
Set<ExternalRecord> getModified(String structure, Date after) throws
EsaException, RemoteException;
/**
* Gets modified records in a paged manner. See {@link #getModified}
description.
* This method is only called if {@link #getModified} call returned NULL.
/**
* Gets IDs of records, deleted in external system after given timestamp.
* ESA may pay or pay not attention to this timestamp. It is OK for ESA to
respond
* to this call with ALL deleted IDs, ever deleted in the system - sync will
handle this properly.
*
* However, this may result in a greater traffic between ESA and EW.
*
* The timestamp corresponds to the the time of the last successful
invocation of getDeleted().
*
* If a ESA accumulates deletion information, it may always return all
accumulated IDs and purge
* them after successful "endSync()".
*
* In any case, this call may result in a great amount of data to be
transferred.
*
* If the ESA predicts such a traffic, it should return NULL from this
method (NOT an empty set!).
* In this case, <ew:shortproductname> <span class="product1">EW</span><span
class="product2">SaaSWizard</span></ew:shortproductname> will make use of {@link
#getDeletedPaged} method.
*
* ESA may decide how to transfer data in runtime (depending on their
amount).
* In any case, <ew:shortproductname> <span class="product1">EW</span><span
class="product2">SaaSWizard</span></ew:shortproductname> will call {@link
#getDeleted} first and only call {@link #getDeletedPaged}
* if {@link #getDeleted} returns NULL
*
* @param structure structure to read
* @param since timestamp
* @return IDs of record, deleted after timestamp
*/
Set<String> getDeleted(String structure, Date since) throws EsaException,
RemoteException;
/**
* Gets deleted records in a paged manner. See "getDeleted()" description.
This method is
* only called if "getDeleted()" call returned NULL.
/**
* Resets cursor expiration timer.
*/
void leaseCursor(String cursorID) throws EsaException, RemoteException;
/**
* Closes the cursor. This call indicates that <ew:shortproductname> <span
class="product1">EW</span><span class="product2">SaaSWizard</span></ew:
shortproductname> doesn't need the cursor anymore and guarantees that
"readDataPage" will never be called for this cursor.
*/
void closeCursor(String cursorID) throws EsaException, RemoteException;
/**
* Gets data from a cursor. This method is used to
* actually access the data in the cursor.
*
* @param pageIndex index of the page to retrieve
* @return data page
*/
Set readDataPage(String cursorID, int pageIndex) throws EsaException,
RemoteException;
/**
* Reads a single record in external system
* @param structure read record of that structure
* @param pk read record with that ID
* @return newly created record. Record must contain at least ID and
ModificationTimestamp information.
*/
ExternalRecord read(String structure, String pk) throws RemoteException,
EsaException, EsaRecordException;
/**
* Creates new record in external system
* @param values record values
* @return newly created record. Record must contain at least ID and
ModificationTimestamp information.
*/
ExternalRecord create(String structure, ExternalRecord values) throws
RemoteException, EsaException, EsaRecordException;
/**
* Updates the record in external system.
/**
* Deletes the record in external system, returns true.
* If the record is modified after <code>lastSeen</code> timestamp,
* throws OptimisticLockFailureException
*
* @param structure structure to create record in
* @param lastSeen last seen timestamp.
* @param pk ID of record to delete
*/
void delete(String structure, Date lastSeen, String pk) throws
RemoteException, EsaException,
EsaRecordException, OptimisticLockFailureException;
/**
* Counts a number of tickets, with IDs in range of [IDmin;IDmax]
(inclusive).
* This method is a callback for {@link HelperApi#detectDeleted(String,
String,java.util.Date)}
*
* @param idMin min ID, inclusive
* @param idMax max ID, inclusive
* @return number of records with matching IDs
*/
int countRange(String externalStructure, String idMin, String idMax) throws
EsaException, RemoteException;
}
package com.supportwizard.sync.interfaces.esa;
import java.rmi.RemoteException;
/**
* Base class for ESA Implementations. Provides some helpers and stubs some
rarely used methods.
*
* @author <a href="[email protected]">Anatoly Kochubey</a>
*/
public abstract class ExternalSystemAdapterBase implements ExternalSystemAdapter
{
/**
* Gets resource bundle for a locale. This method doesn't take default
system locale into consideration,
* so if the default english messages are put into <basename>(.properties),
they are used whenever
* passed locale doesn't match
* @param locale
* @return
*/
protected ResourceBundle getResources(String basename, Locale locale) {
ResourceBundle i18n = ResourceBundle.getBundle(basename, locale, new
ResourceBundle.Control() {
@Override
public Locale getFallbackLocale(String baseName, Locale locale) {
return null;
}
});
return i18n;
}
///////////////////////////////////////
// ExternalSystemAdapter default implementation
/**
* Default implementation calls startSync() and endSync() to verify paramters
* @param externalSystemID externalSystemID to associate ESA with
* @param force force setting externalSystemID
* @return
* @throws EsaException
* @throws RemoteException
*/
public String configure(String externalSystemID, boolean force) throws
EsaException, RemoteException {
// Try new parameters
startSync(externalSystemID);
endSync();
return externalSystemID;
}
///////////////////////////////////////
// Pages - Not implemented by default
/////////////////////////////////////////
// Miscellaneous
FileDirectoryESA
FileDirectoryEsa is an ESA implementation class. Its methods are called on receiving corresponding
messages and it may call the Helper Api to make a call back to the Agiloft server.
package com.supportwizard.sync.file;
import com.supportwizard.sync.interfaces.esa.*;
import java.rmi.RemoteException;
import java.util.*;
import java.io.File;
import java.io.IOException;
import java.io.FileFilter;
import org.apache.log4j.Logger;
/**
* This ESA synchronizes <ew:shortproductname> <span class="product1">EW<
/span><span class="product2">SaaSWizard</span></ew:shortproductname> with files
in some local file directory (or a shared network path).
*
* @author <a href="mailto:[email protected]">Anatoly Kochubey</a>
* @version $Id: FileDirectoryEsa.java.txt 140548 2013-09-13 11:42:12Z alexg $
*/
public class FileDirectoryEsa extends ExternalSystemAdapterBase {
//////////////////////////////////////
// ESA parameter Metas.
/**
* Local path to the file directory to sync.
* There can be multiple directories, separated by local system path
separator.
*/
private final String DIRECTORY_PATH = "path";
/**
* List of "fields" in file, separated by comma. Order is important for CSV
format
*/
private final String FIELD_LIST = "fields";
//////////////////////////////////////
// Parameter values
// Directories to search in
private Map<String, File> directories;
// Field names
private List<String> fields;
// File reader/writer
private FileDataGate dataGate;
return ExternalSystemAdapter.SYNC_VERSION_1_0;
}
if(!directory.isDirectory()) {
throw new EsaConfigurationException("Not a directory " +
directory.getAbsolutePath());
}
directories.put(directory.getAbsolutePath(), directory);
}
//////////////////////////////////////
// CRUD
log.debug("Read file");
File newFile = new File(directory, pk);
try {
return dataGate.read(newFile);
} catch (IOException e) {
throw new EsaRecordException("IO error", e, newFile.
getAbsolutePath());
}
}
// Do data save
File newFile = null;
try {
// Create new file
if(values.getId() == null || "".equals(values.getId().trim())) {
newFile = File.createTempFile("sync", "ew." + dataGate.
getDefaultFileExtension(), directory);
} else {
newFile = new File(directory, values.getId());
if(newFile.exists()) {
throw new EsaRecordException("Clashed IDs: File " + values.
getId() + " already exist", values.getId());
}
newFile.createNewFile();
}
log.debug("Created: " + newFile.getAbsolutePath());
dataGate.create(newFile, values);
return dataGate.read(newFile);
} catch (IOException e) {
throw new EsaRecordException("IO error", e, newFile == null ?
"NoFile" : newFile.getAbsolutePath());
}
}
// Do data save
File file = null;
try {
// Find the file
File[] found = directory.listFiles(new FileFilter(){
public boolean accept(File pathname) {
return pathname.getAbsolutePath().equals(values.getId());
}
});
if(found == null) {
log.debug("Nothing found");
found = new File[0];
}
if(found.length == 0) {
// File is deleted. Now report as optimistic lock failure
log.debug("No file found");
throw new ConcurrentDeleteException(values.getId(), new Date
(System.currentTimeMillis()));
}
// File is found
file = found[0];
assert file != null;
if((file.lastModified() / 1000) * 1000 > lastSeen.getTime()) {
// Optimistic lock failure
log.debug("File is concurrently modified");
throw new OptimisticLockFailureException(values.getId(), new Date
(System.currentTimeMillis()));
}
public void delete(final String structure, final Date lastSeen, final String
pk) throws RemoteException, EsaException, EsaRecordException {
File directory = directories.get(structure);
if(directory == null) {
throw new EsaException("Unknown directory " + structure);
if(found == null) {
log.debug("Nothing found");
found = new File[0];
}
if(found.length == 0) {
// File is deleted. Now report as optimistic lock failure
log.debug("No file found");
throw new ConcurrentDeleteException(pk, new Date(System.
currentTimeMillis()));
}
// File is found
// Do deletion
file.delete();
}
////////////////////////////////////////
// READ
if(found == null) {
log.debug("Nothing found");
found = new File[0];
}
return result;
} catch (IOException e) {
throw new EsaException("IO error", e);
}
}
return allKnownIDs;
}
//////////////////////////////////
// Meta information
FileDataGate
The FileDataGate interface referenced in FileDirectoryEsa is a pure implementation class. The example
ESA uses the FileDataGate interface implementation to access plain text and XML property files. This is a
FileDirectoryEsa implementation specific interface, not a sync interface.
/**
* Abstract reader-writer of different file formats
*
* @author <a href="mailto:[email protected]">Anatoly Kochubey</a>
* @version $Id: FileDataGate.java.txt 96044 2010-02-23 08:16:34Z gof $
*/
public interface FileDataGate {
/**
* Closes all open handles
*/
void closeAll();
/**
* Updates file with data
* @param file
* @param values
*/
void update(File file, ExternalRecord values) throws IOException;
/**
* Reads file data into ExternalRecord
* @param file
/**
* Creates new file
* @param values
*/
File create(File directory, ExternalRecord values) throws IOException;
JavaExecutableEsa
JavaExecutableEsa is the main class of the command-line application. You may modify line 28 - new
StreamTransport(true, System.in, System.out) - replacing it with new HttpXmlEsaTransport
(externalSystemID, ewHost) to transform the example ESA into an independent ESA, connecting over
HTTP. You will have to supply or hard-code ExternalSystemID and Agiloft hostname:port.
To convert an example into full-featured ESA, you only have to provide an implementation of the
ExternalSystemAdapter interface and modify lines 25 and probably 29 of JavaExecutableEsa.
Other sources, including XML parsing/generating, message dispatching, etc, are available as a reference within
example.jar.
1 package com.supportwizard.sync.remoteesa;
2
3 import com.supportwizard.sync.interfaces.transport.*;
4 import com.supportwizard.sync.interfaces.transport.EsaReceiver;
5 import com.supportwizard.sync.interfaces.
transport.invokers.JavaEsaInvoker;
6 import com.supportwizard.sync.file.FileDirectoryEsa;
7
8 import org.apache.log4j.Logger;
9
10 import javax.xml.bind.JAXBException;
11 import javax.xml.bind.JAXBContext;
12
13 /**
14 * @author Anatoly Kochubey
15 * @version $Id: $
Database Interface
© 2022 Agiloft Inc. 284
Database Interface
This section describes the steps required to connect via ODBC to a MySQL embedded database instance installed
together with the standard Agiloft®installation.
Please note, we do not generally recommend accessing the database directly. The main reasons for this are:
Agiloft is a high-level business-oriented information management system. There are many layers of logic between
the raw data in the database and what the user sees and is able to export from the GUI.
The Agiloft database is managed by the application itself, rather than a DBA. This results in "machine-generated"
database structures which may not correspond to what is presented in the GUI in any obvious way. The
"management" processes occur at runtime and the database can "morph" depending on the actions of the admin
user: columns can be added, removed or change type.
Further, during the upgrade process Agiloft may modify the database structures to bring them up-to-date with the
definitions that are expected by the code. There is therefore a risk of the external reporting engine failing to work
with an updated database.
Finally, certain fields and values, artificial columns, are calculated on-the-fly and are not present in the database at
all or are present in different tables.
Under Microsoft Windows operating system the file is in your SystemDrive root directory e.g. C:
\EnterpriseWizardConfig.xml
Under Linux and UNIX operating systems the file is in /etc directory i.e. /etc/EnterpriseWizardConfig.
xml
The file is an XML document, the database related information is stored under "ewDatabase" XML tag.
<ewconfiguration>
<ewInstallStatus>2</ewInstallStatus>
<ewHome>/usr/local/EnterpriseWizard</ewHome>
<ewDatabase>
<databaseServer>1</databaseServer>
<databaseServerType>mysql</databaseServerType>
<databaseServerHome>/usr/local/EnterpriseWizard/mysql</databaseServerHome>
<databaseAdminLogin>root</databaseAdminLogin>
<databaseAdminPassword>AsaUR4k</databaseAdminPassword>
<databaseEwLogin>ewuser</databaseEwLogin>
<databaseEwPassword>AsaUR4k</databaseEwPassword>
<databaseReaderLogin>ewreader</databaseReaderLogin>
<databaseReaderPassword>GGha42l</databaseReaderPassword>
<databaseName>sw2_std</databaseName>
<connectionType>1</connectionType>
<databaseAddress>127.0.0.1</databaseAddress>
<databasePort>3333</databasePort>
<socketFile>/var/lib/mysql.sock</socketFile>
</ewDatabase>
...
For example, you will find following two values are specified in the Agiloft configuration file mentioned above:
<databaseServer>1</databaseServer>
<databaseServerType>mysql</databaseServerType>
This user is created during install and the password is generated during install and changed on update. By default
the administrative level user is "root".
<databaseAdminLogin>root</databaseAdminLogin>
<databaseAdminPassword>AsaUR4k</databaseAdminPassword>
For more information, read how to Encrypt and Decrypt the Database Password.
Notes
If the database is recreated during upgrade this user will be lost and will have to be re-created.
This however only happens when the version of MySQL is upgraded and if the upgrade requires data to be
recreated.
1. From the Agiloft host log into embedded MySQL instance as administrative level user:
i.e
3. Verify connectivity with the new user by logging into MySQL again:
<databaseServerHome>/bin/mysql -u_USER_ -p
--socket=<databaseServerHome>/ewdbsocket -A sw2_std
<databasePort>3333</databasePort>
If the access to this port is denied by a firewall there will be no response. Press Ctrl+C to stop telnet.
http://dev.mysql.com/downloads/connector/odbc/
Be sure to pick the correct driver version that corresponds to your Operating System. Choose Typical Installation if
given a choice by the installer. The installation is rather straightforward, the following articles may be helpful as
reference for various OS's.
Linux: http://www.easysoft.com/developer/interfaces/odbc/linux.html.
You may want to configure the datasource as System - available to all users.
The important thing to remember is to specify the correct user, password and port.
Be sure to test the connection - both Windows and Linux/UNIX connection managers provide this functionality.
Query Tables
© 2022 Agiloft Inc. 291
Query Tables
You can find out the database-level name of Agiloft tables in Agiloft GUI directly via Table Wizard: Setup > Table >
YOUR TABLE > Edit/General > TableName. For example the database name for table Support Case in standard
Demo KB is "sw_9991f_case".
However, these names may change during upgrades or if you export/import the knowledgebase onto a new server.
A more reliable method is to obtain the physical table name from the Logical Table Name that is provided in the
GUI: Navigate to Setup > Table > YOUR TABLE > Edit > General tab. Use the following query to obtain the
physical table name:
+----------------+
| dbname |
+----------------+
| sw_20288f_case |
+----------------+
Please be aware that since Agiloft is a business-level information management system not all data available via
GUI in a certain Agiloft table can be directly found in the corresponding database table.
1. Some of the data is in a serialized format unsuitable for direct queries or linking, for example multi-value
linked fields.
2. When a column of a record in the target table imports a column of a record in a donor table on the level of
database this is expressed with indirect links via a linking table.
EW caches the values of imported fields such as the "Updated By" field in the local table to improve
performance when searching or reading records, so the data from such fields is available in the target table
using the Field DBName shown in Setup > Table > Edit >Fields tab.
However if the application requires extra data from the source table, obtaining it becomes more difficult.
Since the link is defined on the level of meta-data in order to use such relationship to build a true "join"
between two tables an external application would have to repeat the logic of linking Agiloft knowledgebase
meta-data with the linking table. Instead please apply the following workaround.
There is one feasible way of querying tables linked in Agiloft directly. It will only work on single-value
single- or multiple-column linked fields. In the GUI one has to add the ID field to the set of fields
imported from the donor table into the target one. This will cause a column to be added to the target
table. Though there is no strict relationship on the database level the values in this column can be
used to forge links between two tables in the external reporting tool.
3. Choices values are stored as identifiers in the database. These identifiers may change when the
knowledgebase is reconfigured by the administrator. In general there is no explicit guarantee that this
internal values will stay the same during the lifetime of the product/knowledgebase.
As such these should be resolved against swchoicelines table bound by the desired value
in "choicelinetext" column, linked to swchoices table via "choiceid" column, bound by the name of the search
in "name" column and linked to swproject table via swprojectid column, bound by the name of the
knowledgebase in the name column. The name of the search can be found in GUI via Setup > System >
Manage Choice Lists > Choice Name.
The following query obtains the ID that is used to represent the Yes value in the Demo KB for the yes_no
choice list:
Note: The commands are platform independent, apart from the direction of the slashes.
To encrypt a new password in the console, run the following command, replacing $ALHOME with the
actual installation directory and newrootpassword with the actual password: $ALHOME/jre/bin/java -
jar $ALHOME/resources/java/lib/encrdecr.jar -e newrootpassword.
To add the encrypted password, edit EntepriseWizardconfig.xml and replace the XML tag
databaseAdminPassword with the new value: <databaseAdminPassword>-
af6493d3a1c236cc3b5248210e0204e</databaseAdminPassword>.
To decrypt the password in the console, enter the following command, replacing the given value with the
one inside the databaseAdminPassword tag: $ALHOME/jre/bin/java -jar $ALHOME/resources
/java/lib/encrdecr.jar -d 5dcd1311d8854747207a6df87216de44
These tips are provided for DBAs to make use of. If you aren't familiar with an option listed, do not use it.
Required or Recommended
Start with these steps, which are required or recommended:
Make sure that XA transactions are enabled to correctly process JMS work. This is required.
We also recommend using a separate MSSQL instance for Agiloft. This prevents inappropriate locks by
other products within the same MSSQL instance, and it allows you to reserve the sysadmin role for aluser.
Optional Optimizations
Consider trying any or all of these methods to optimize your system or resolve issues:
At Database Properties > Options, set the recovery model to Simple. If for some reason you must use a full
recovery model, configure a periodic backup to avoid constant transaction log growth.
For versions below MSSQL2016: At Database Properties > Files, set Autogrowth to 200Mb+ for data and
64Mb+ to log. Set log autogrowth for tempdb to 64Mb+.
At Server Properties > Processors, set Maximum Worker Threads to 2048 and set Boost SQL Server
Priority to On. Note that this might cause some instability.
For versions below MSSQL2014: At Server Properties > Memory, set the maximum server memory to half
of server memory.
Finally, it is highly discouraged to use a separated architecture, with JBoss at one server and MSSQL at another.
However, if this is required for some reason, consider these tips for managing it:
Use two virtual machines at one host, or use a host and guest as two machines, with the appropriate network
interface. For example, use host-only networking for VMWare.
Troubleshooting Tools
If none of the other options resolve MSSQL-related issues, you could choose to try these options as well. These
options are not recommended for production environments and might affect performance.
These methods are not safe for production and should be used only after exhausting all other options and
taking appropriate precautions.
Stop all MSSQL services from automatic execution except SQL Server (sqlservr.exe).
Alter the "sw2_std" database to set auto_shrink on. It is also worth shrinking the tempdb data file (the
transaction log cleaned automatically on every MSSQL restart) from time to time.
Custom Scripts
© 2022 Agiloft Inc. 296
Custom Scripts
Scripts can extend the function of your knowledgebase by automating events such as moving files to a specific
directory on the server. Scripts are executed by creating a script action within a rule or action button.
Script execution
Scripts are automatically executed by the correct interpreter based on their file extension:
Record created/removed/modified through the GUIuser record: a record that has been created/removed
/modified and matched our filter.
1. Validation is performed on the record at the browser level, for example to confirm that the user has provided
value for all required fields.
2. The validity of changes made by the user is checked based on group permissions.
Note: This is an important security feature. Although Agiloft only sends to the browser the fields that the user
is permitted to see and only displays the fields for which the user has edit permissions, a hacker might
construct their own http stream by hand in an attempt to circumvent this protection. If any of the changes are
invalid, the record update is cancelled and an error message is displayed.
3. If the user has changed the workflow state, any associated scripts are called. These scripts may modify
fields in the record and act with absolute authority. In other words, they may set field values that the user
would not be allowed to set based on his group permissions.
4. The record may trigger business rules that execute automatic actions and any associated scripts. The order
in which the business rules are checked is based upon the Priority field in each rule, so if a business rule
with high Priority modifies the record, a rule with lower priority will look at the modified record before deciding
whether or not it should execute.
Summary based: number of user records matching given filter [operator][threshold], where operator is one of <, >,
<=, >=, = and threshold is a number.
user record: all records meeting the filter. User can tell Agiloft to run actions either for all matching records, or only
for one of them (see rule settings).
user action: user record creation/removal/modification after which number of matching records exceeded the
threshold.
Timer based: periodically executed summary, number of records matching the filter>0.
However, it is not necessary to fill the linkholders - they will be reconstructed automatically from the values supplied
to one or more of the Linked Field data columns. It is important here NOT TO COPY a set of values from the source
record and change just some of them, as it may lead to links not being restored correctly.
In perl/exe scripts Linked Fields are exported like any other fields together with the linked record from the donor
table. Please note, that the linked record is exported only for a single Linked Field for performance reasons.
To create a link it is enough to set a value to one of the Linked Field data columns, which is done in the same way
as for any other field. For example, if you want to change the Assigned To for a record and the Linked Field data
column is named 118_f_login; you can find this column name in Table wizard on Fields tab in column "Field name";
then put the following to the output file:
EWset::setRecordField('_118_f_user_login','Umbra');
The script running engine will try to find the best matching record in the donor table and set the Linked Field link to
that record. It works the same way for "multiple fields from other table" LF, but you are free to fill only a subset of LF
columns. For example, if there are fields User Login and User Phone exported to your table, you may provide a
value just for the User Login if you are sure it will result in an exact match.
This will always work for user login, since they are unique across each KB, but other fields could contain repeating
values. In this case one has to fill additional fields to achieve a unique match, otherwise the first suitable match will
be used.
If no match at all is found, the link will be left empty.
a set of global variables including current user's record from Contacts, same as used in saved searches and
formulas.
script event source that could be on of the following:
WEB_WORKFLOW - script is triggered by workflow while record was edited in GUI
WEB_BUSINESS_RULE - script is triggered by rule while record was edited in GUI
EMAIL - script is triggered when record was created from inbound email
API - script is triggered when record was created or modified with Web Services
TIMER_BUSINESS_RULE - when script is triggered by rule based on schedule
current record values. Permissions won't be applied either on reading or on saving. Scripts can also block user
action or force user log-out. If a user record contains linked fields, then corresponding records from the linked tables
are also provided. For example, if we have a record from Cases table, that contains assigned_to field= "John" which
references Contacts table, then John's record from Contacts table will be also included. Only single linked records
are exported, i.e. communications tables and any other multiple value LF records will not be exported. This
constraint is applied for scripts on perl, .exe scripts and all other languages that interact with Agiloft via .xml files.
[optional] old record values, available when script run in response to "record modified" event. This information
allows a script to understand what fields were actually changed by the user, or by preceding rules-triggered actions.
Script should output:
exit code – either in XML or as standard exit code. All other parts of output are optional. See DTD for output
XML file.
[optional] new values for record. Please note, that it is not necessary and even not recommended for some
cases, like Linked Fields with multiple columns, to "copy" input records data to output. It is enough to output
only the fields your script changes.
[optional] debug message - will go to Agiloft log.
[optional] user message - will be shown to the user in the case of non-redirecting exit code - as error
message if the script blocks the change or as status message in table view if change accepted by script.
[optional] redirect-URL to redirect user browser in the case of redirecting exit code.
[optional] report-message to store in record history to describe script actions upon the record.
More detailed description of input and output formats is provided in the next chapters.
Stringifiers Description
© 2022 Agiloft Inc. 300
Stringifiers Description
1. For each type (Date/Datetime/Time) there are list of patterns, which are used to parse.
a. Date "dd/MM/yyyy", "MM/dd/yyyy" , "MM/dd/yy", "dd/MM/yy", "MMM dd yyyy", "MMM dd yy", "dd MMM
yyyy" , "dd MMM yy"
b. Datetime "dd/MM/yyyy HH:mm:ss", "MM/dd/yyyy HH:mm:ss", "MM/dd/yy HH:mm:ss", "dd/MM/yy HH:
mm:ss", "MMM dd yyyy HH:mm:ss", "MMM dd yy HH:mm:ss", "dd MMM yyyy HH:mm:ss", "dd MMM
yy HH:mm:ss"
c. Time "HH:mm:ss"
2. If the string can't be parsed using any of these patterns then the converter tries to parse its value as a
number, which represents time in milliseconds.
3. If finally string can't be parsed - then exception is thrown.
4. Parsed date is converted to KB time zone and current locale.
singleInputBox - means show single input box. No input boxes for days, hours, etc.
round - means round to a certain number of digits, for example, setting "round" to 2 would round 5.66666 to 5.67
All the above properties can be configured from the Setup > Tables > Edit > Fields screen.
Conversion to string is quite simple, the algorithm is as follows:
So, possible values for 1 day 2 hours and 23 min 15 seconds could be:
1: 2 - when singlebox is enabled, decimal hours are enabled and show minutes with seconds is disabled.
1 day 2 hours 23 minutes 15 seconds - when singlebox is disabled and we are showing all time units.
1 day 2.0 hours when singlebox is disabled and decimal hours is enabled with round/padding equal to 0 etc.
depending on current settings of the column.
You can't modify record links by passing new set of linked IDs on save. An empty string represents an empty link
set.
Choice fields
Choices are shown as simple text strings (note that choice lines cannot contain commas inside their labels).
Multichoice fields
Multichoices are shown as a text strings separated by commas (hence the choice line itself cannot contain comma
inside its label).
Where $recordId1 and $recordId2 are IDs of the donor records these values are from.
Scripts may be created in any standard programming language such as Perl, Java, C++, or Basic and run by
Business Rules at Setup > Rules or Workflows at Setup > Workflow. These wizards tell you in which directory on
the server the script must be placed. For security reasons, it is not possible to upload the script to the server using
the GUI; the script must be installed directly on the Unix or Windows file system.
If you would like to use your own Perl distribution with scripts rather than the default distribution, you can define the
location of your preferred perl.exe using the Location of external Perl directory global variable. For example, you
might set it to c:\Perl64\bin.
Scripts can:
Set field values in the record. For example, a script can apply sophisticated logic to automatically assign new
Cases to the correct member of staff.
Logout the user and redirect his or her browser to a specific URL.
Verify the data being entered by comparing it against data in an external file or database and, if necessary,
block the requested change to the record.
Write to an external program or file or generate email.
Return personalized messages based on field values, such as "Hi Bob, thank you for the input"
Accept or reject changes to the record made by the user.
Field values of the record that is being modified. For example, Severity.
Field values that record held prior to the current modification. For example, the old value of Severity.
Field values from the Contacts table of the user who is modifying the record. For example, Full Name.
Field values of any records that are linked to the record that is being modified. For example, the Full Name of
the person that the record is currently Assigned To.
Global variables, such as the name of the KnowledgeBase.
Modification event source. The possible values are: WEB_WORKFLOW, WEB_BUSINESS_RULE, EMAIL,
API, TIMER_BUSINESS_RULE.
Perl API
© 2022 Agiloft Inc. 304
Perl API
The Perl API makes it easy to write custom scripts. It consists of two modules - EWGet.pm and EWSet.pm - for
reading input and writing output files.
There are many ready-made functions available, but many scripts only use 6 functions: EWget: load, EWget::
getValue, EWset::setRecordField, EWset::setMessage, EWSet::setExitAction and EWset::save, and two of these,
'load' and 'save', are identical for all scripts. If you just master the functions EWget::getValue, EWset::
setRecordField, EWset::setMessage and EWSet::setExitAction, you will be able to write very powerful scripts.
Many of the functions take a parameter $recordName as an input variable. There is always a record named
"current_state", which is the record that is currently being edited. In addition, the script may have available to it
"old_state", which provides the old field values of the record and other $recordNames, which provide the values of
records that are linked to the input record via linked field relationships.
EWget Module
© 2022 Agiloft Inc. 305
EWget Module
EWget::load($fileName)
Loads the input file into internal data structures, you will be calling it like this:
Example
Comments
This is the first thing you must do before any further calls to EWget functions. This call is the same for all
scripts.
EWget::getValue($recordName, $fieldname)
Returns the value for the field of the record.
Example
EWget::getValue('current_state', 'status')
EWget::getValue('old_state', 'status')
Retrieves the old value of the 'status' field, i.e its value prior to any changes made by the user:
Comments
This is the core method for accessing field values, but see also EWget::record($path), it is a more elegant
method for retrieving the fields of the 'current_state' record.
EWget::getGlobalVariable($variableName)
© 2022 Agiloft Inc. 306
EWget::getGlobalVariable($variableName)
Returns the value of the specified variable. For example:
Example
The following function retrieves the value of the "my_first_name" field in the contact entry of the user who
caused the script to run.
Comments
Global variables contain KB-wide settings along with user-specific settings, they are also used in saved
searches and formulas.
EWget::getLinkedValue($recordName, ...)
Returns value of a field from a linked record.
Parameters:
Example
To find the value of the "email" field of the employee responsible for the given case:
my $responsible_person_email=
EWget::getLinkedValue("current_state", "assigned_to", 0,
"email");
Comments
This is the core method for accessing the data in linked records.
EWget::record($path)
© 2022 Agiloft Inc. 307
EWget::record($path)
Returns the value of a field in a record.
This function provides a "compact" navigation through the record data for the "current_state" record.
Example
"email.username" - returns the value of the "username" field for the first referenced record from
current_state.email
So:
EWget::record(email.username)
is equal to
EWget::getLinkedValue("current_state",
"email", 0, "username");
Comments
As the above example shows, EWget::record() is not really necessary, but it is certainly convenient.
EWget::getCallInitiator()
Returns a string that indicates the action that caused the script to be called.
WEB_WORKFLOW-A workflow action, typically initiated by a user changing the Workflow State in the GUI.
EMAIL-Inbound email
Comments
EWget::getGlobalVariablesNamesList()
Returns a sorted array of global variables names.
Example
Comments
You will usually know what global variables you want to use in your scripts, so this function is mostly for
debugging purposes and to find out what variables are available.
EWget::getFieldsNamesList($recordName)
Returns list of field names for record by record's name.
Example
Comments
EWget::getRecordsNamesList()
Returns an array with the names of all the records provided to the script.
The following code dumps all the records names from the input file passed to the script.
Comments
This function is mostly used for debugging purposes. There always will be a record named "current_state"
and possibly one named "old_state" (if the current record is being modified, rather than created). There may
also be records that were linked to the current record.
These are named "recXXX", where XXX is an incremental number starting from 1.
EWget::getLinkedRecordsNamesList
($recordName, $fieldName)
Returns sorted list of names of records linked via a "linked" field.
Comments
This function is mostly used for debugging because you will probably know the names of the records that you want
to access before writing the script.
Only the linked records where the link does not allow multiple values are passed to scripts, so this function will
return an array of 0 or 1 entry in a form of "recXXX" string.
In most cases you will already know what tables your records are from, but in case of the linked field type "single
field from multiple tables" this function will allow you to determine what table the associated record is from and
handle it appropriately.
EWset Module
© 2022 Agiloft Inc. 311
EWset Module
EWset::save($outfileName)
Processes changes requested by the user and stores them in an xml file.
You should call this method at the end of your script to make your changes available to Agiloft. For example, the
script may have changed the value of a field, this change will be ignored, unless it calls "save" prior to exiting.
Example
If you used
EWget::save($output_fname);
EWset::setRecordField($fieldName, $fieldValue)
Assigns a value to the specified field in the record or sets the field to NULL if the value is undefined.
Example
EWset::setRecordField("email",
"[email protected]");
Comment
This is the core function for change data in a your script.
This function manipulates both common fields and linked fields.
See Stringifiers Description for full description of acceptable formats for each type of data.
"AcceptChanges" - the record is updated in the normal fashion. You can use setMessage to inform user
about the details of update.
"RejectChanges" - the modification is rejected. User will be shown the record editing window, if the action
was initiated via web interface.
"RejectChangesAndExit" - the modification is rejected and the user is automatically logged out. After
logout, user will be redirected to URL specified by setRedirect function call.
"AcceptChangesAndExit" - the record is updated in the normal fashion and the user is automatically
logged out. After logout, user will be redirected to URL specified by setRedirect function call.
Alternative to setExitAction
As an alternative to setExitAction, provided for compatibility with languages that do not allow strings to be returned,
the script can provide an exit (return) code.
If the exit code is 0, the record is updated in the normal fashion. You can use setMessage to inform user
about the details of update.
If the exit code is 1, the modification is rejected. User will be shown the record editing window, if the action
was initiated via web interface.
If the exit code is 2, the modification is rejected and the user is automatically logged out. After logout, user
will be redirected to URL specified by setRedirect function call.
If the exit code is 3, the record is updated in the normal fashion and the user is automatically logged out.
After logout, user will be redirected to URL specified by setRedirect function call.
Formally speaking this function sets the value for the tag 'exitAction'.
EWset::setMessage($userMessage)
Send a message to the user.
This message will be shown to the user if the script is executed because the user has changed a record in GUI.
Formally speaking, this method sets the value for the tag 'message'.
When a script blocks a record edit, it is a good practice to use setMessage to explain why the change was blocked
and what the user should do. Of course, it can also be used to confirm success or provide some other message.
EWset::setRedirect($redirectUrl)
Sets the value of the URL that the user should be directed to if the script logs them out of Agiloft
The redirect URL should be absolute and include "http". It will be opened in the same browser window. Formally
speaking, this method sets the value of the tag 'redirect'.
Example
EWset::setRedirect("http://www.enterprizewizard.com")
It is useless to call setMessage() with setRedirect since there will be no Agiloft page shown to display the message.
EWset::setDebug($debugMessage)
Write to the log file (formally speaking, sets value for tag 'debug').
This message is saved to the Agiloft log. It can be useful for logging the result of a script or to debug the script.
EWset::setReport($reportMessage)
Sets a value in the record history
Note: It is sometimes helpful to write a description of what the script did to the data and why.
Example
The function is able to get an exitCode directly, as $exitAction argument, thus the following pairs of values
are equivalent:
"AcceptChanges" or 0 or "0"
"RejectChanges" or 1 or "1"
"RejectChangesAndExit" or 2 or "2"
"AcceptChangesAndExit" or 3 or "3"
Scripts take 2 command line arguments: names of the input and output files. Input and output files are in XML
format. Note that the output file is precreated by Agiloft but is empty.
Input file conforms to the following DTD:
<!DOCTYPE input[
<!ELEMENT input project_name, old_state?,
linked_records, global_vars)>
<!ELEMENT project_name (#PCDATA) >
<!ELEMENT old_state (record) >
<!ELEMENT current_state (record) >
<!ELEMENT linked_records (record) *>
<!ELEMENT global_vars (variable) *>
<!ELEMENT record (field) +>
<!ELEMENT variable (value) >
<!ATTLIST record
table CDATA #REQUIRED
xml: id ID #REQUIRED>
<!ELEMENT field (value) >
<!ATTLIST field
name CDATA #REQUIRED
linked_refid IDREFS #IMPLIED>
<!ELEMENT value (#PCDATA) >
<!ATTLIST variable
name CDATA #REQUIRED>
]>
For Perl scripts there are interface modules for interaction with Agiloft provided; see full description below.
This file describes the script result, with assigned_to field value set to "Umbra", summary set to "some summary
here", changes accepted to be put to database, and "Script accepts your data" message is shown to user.
Both files always use UTF-8 encoding. Input file uses xml: id; see http://www.w3.org/TR/2004/WD-xml-id-20041109/
for assistance with referring to the linked records. Things like project and table names and values from user record
are not translated into the user's language. When converting user record into XML format Agiloft uses so-called
Stringifiers from com.supportwizard.util.stringify package.
In short, for every column type there exists a stringifier that can translate the internal data representation into String
and vice-versa. For instance, if there is a Choice 'priority' column that contains 'Medium' value, it's internal
representation is Medium's ID and choices stringifier will convert this ID into 'Medium' string for scripts, - then some
script can change 'Medium' into 'High' and stringifier will convert it back into 'High's ID. This implies that values
produced by scripts must be understandable by stringifiers. See the appendix for full stringifiers info.
All of the output file fields are optional, except for <redirect> that must be present if exit code is 2 or 3; see below. If
there 's no <record>, then it is considered to be unmodified. <debug> only sends it's content to system log. <report>
provides data for a report that will appear in the history tab. <message> and <redirect> are covered below.
The main thing that Agiloft expects from a script is its return (exit) code. There are 4 possible values:
0 means success. If <message> is presented, it is shown to the user. <redirect> is ignored, all modifications
from <record> take their place.
1 leads to user action block. If <message> is presented, then it will be shown to the user. <record>
modifications will be ignored.
2 will cause action block, user log-off and redirection. <message> will be logged, and user will be
redirected to <redirect>. <record> modifications will be ignored.
3 leads to user log off and redirection, but keeps changes. This is similar to 2, but action won' t be
blocked and changes made by script will take place.
It is also possible to use <exitAction> to override the exit codes. Recognized values are:
If exitAction is present, it overrides the "Raw" exit code. For instance if <exitAction>is set to AcceptChanges and
exit code is 1, changes will be accepted.
Note: The Agiloft distribution includes Perl and will automatically use it to execute scripts with a .pl subscript. If you
want to use your own version of perl, you may do so by specifying it on the first line of the script, eg #!/usr/local/perl.
######################################################################
# Synopsis:
#
# Redirect the user after he/she submits the ticket.
#
# There is no need to specify the Perl interpreter if the script is named *.pl
# because the system recognizes .pl scripts as being Perl based and will
# use the Perl that is included in the distribution.
#
# If you use this script with your own copy of Perl, please note that
# it requires SimpleXML module to be installed
#
######################################################################
use strict;
use warnings;
use EWget;
use EWset;
my ($input_fname, $output_fname) = @ARGV;
EWget::load($input_fname);
######################################################################
#
# Only log the user out if he/she is not a member of the Staff
# group. In other words, we want all members of staff to be able
# to log tickets on behalf of users without being logged out.
######################################################################
my $groups = EWget::getGlobalVariable('my_groups');
my $groupAllowedToClose = "Staff";
if ($groups !~ m/(^|,)$groupAllowedToClose(,|$)/){
EWset::setRedirect("http://www.example.com");
EWset::exit_ew("AcceptChangesAndExit", $output_fname);
} else {
EWset::exit_ew("AcceptChanges", $output_fname);
}
######################################################################
# Synopsis:
#
# Redirect the user after he/she submits the ticket.
#
# There is no need to specify the Perl interpreter if the script is named *.pl
# because the system recognizes .pl scripts as being Perl based and will
# use the Perl that is included in the distribution.
############################################################################
# Synopsis:
# Check if the user belongs to QA group and block closure of the ticket if
# he/she doesn't.
#
# There is no need to specify the Perl interpreter if the script
# is named *.pl because the system recognizes .pl
# scripts as being Perl based and will
# use the Perl that is included in the distribution.
#
# If you use this script with your own copy of Perl, please note that
# it requires SimpleXML module to be installed
############################################################################
use strict;
use warnings;
use EWget;
use EWset;
my ($input_fname, $output_fname) = @ARGV;
EWget::load($input_fname);
my $groups = EWget::getGlobalVariable('my_groups');
my $groupAllowedToClose = "QA";
if ($groups !~ m/(^|,)$groupAllowedToClose(,|$)/
&& EWget::getValue('current_state', 'wfstate') eq "Closed"
&& EWget::getValue('old_state', 'wfstate') ne "Closed"){
######################################################################
# Synopsis:
# This script performs some common tasks typical of a script.
# You could run this script against the input file example:
# filename.pl input.xml output.xml
#
# There is no need to specify the Perl interpreter if the script
# is named *.pl because the system recognizes .pl
# scripts as being Perl based and will
# use the Perl that is included in the distribution.
#
# If you use this script with your own copy of Perl, please note that
# it requires SimpleXML module to be installed
#
######################################################################
use strict;
use warnings;
# interface modules to interact with input and output xml files
use EWget;
use EWset;
# Retreive files names from arguments...
my ($input_fname, $output_fname) = @ARGV;
# and load input into internal structures. This is required step before
# accessing input by API calls.
EWget::load($input_fname);
# we will use this to accumulate messages to user
my $message = "";
# Let's retreive user name to provide personalized message.
# That would be a value of Full Name from
# Contacts table entry for user, editing the ticket.
my $full_name = EWget::getGlobalVariable('my_full_name');
# Let's check if user is a creator of record and prohibit
# the editing if he is not, with respective message.
# The same could be done with permissions, but access
# control through scripts can be more sophisticated.
# For example, you could use EWget::getGlobalVariable('my_start_work')
# and EWget::getGlobalVariable('my_stop_work')
# (if there are such fields in Contacts table) to allow
# modifications only in working time.
open (output_file, ">/tmp/aaaa");
print output_file EWget::getGlobalVariable('my_full_name') . "\n";
print output_file EWget::getValue('current_state', 'created_by') . "\n";
Java Scripts
© 2022 Agiloft Inc. 339
Java Scripts
Scripts can also be written in Java, which allows tight integration with Agiloft and direct access to all user data. Note
this page refers to scripts written in Java, not JavaScript.
Java scripts are a flexible and performance-effective way to implement your custom logic. You can retrieve only the
necessary data and also skip export to xml, running external program and the import results steps. The result is a
significant performance boost in the case of complex tables structure with a lot of linked fields.
This interface has only one method: ScriptOutput runScript (final ScriptInput input) throws
ActionException;
Input Data
A ScriptInput instance provides input data: project and user table ID 's, user's Seance and the record data.
The record data is passed in a map form - an instance of com.supportwizard.dml.SWDataMap class. Specific field
values can be accessed by name, as visible in GUI in Setup > Tables > [Select Table] > Edit > Field. The script
gets one or two instances of the data map - the "old" one and the "new" one. At least one will be present,
depending on when the script is invoked - create - only "new", delete - only "old", modify - both.
This is the mapping of specific data types returned for different field types:
A linked field that imports N field from the source table into the target one will be represented by N+1 entries in the
"input" Map on the data level.
If a single record is imported each of the N entries corresponding to N fields will have a single value of
corresponding type. If a multiple records are imported, each of the N entries corresponding to the N fields will have
an instance of SWDao3MultiValue, a list of String values, one for each imported record, with "_{NULL}_" value
marking the null one. The sequence of values in different multi-value fields is the same for the same set of imported
records.
The extra entry mentioned above will be under the name that can be seen in the GUI in Setup > Table >
<yourtable> > Edit > Fields > <yourLF> > Edit > Fields at the bottom "Linked Column Name". It will contain an
instance of the SWDao3LinkHolder - essentially an array of identifiers for the records in the source table(s).
A script call always starts with the input object and is being called for a single record only. A script however can use
the CRUD+S (select) API underlying WebServices and REST access - SimpleAPI.
Output data
A ScriptOutput instance describes script output, an instance of this class is intended to be created with the public
ScriptOutput - final ScriptInput input - constructor. ScriptOutput can optionally contain modified user record data -
don't change ScriptInput.getRecord() instance.
Note: Do not copy any values your script leaves unchanged from the input record to output. Doing so is
unnecessary and could have unexpected results if you are filling linked fields with multiple columns.
BLOCK_REDIRECT_CODE will also block user action and will force the current user to log-off. The user will be
redirected to ScriptOutput.getRedirect(). ACCEPT_REDIRECT_CODE will logoff the current user, but won't block
an action and script changes will take place.
Unhandled exceptions propagate to Agiloft where they are caught, logged and displayed to the user depending on
the way the script was run. This display is often too verbose and is not really suited for ordinary users.
Thus the recommended way of handling the exceptions is either suppress them in the script itself and instead return
a ScriptOutput instance with a blocking exit code and an optional message or wrap in action-related exceptions.
When wrapped exceptions can be used to change control flow. A plain ActionException means some system-level
error and leads to transaction rollback. Throwing a ActionBlockedException String message amounts to returning
BLOCKED_CODE with ScriptOutput.getMessage() being 'message'.
Throwing ActionBlockedException - String message, String redirectURL - will do the same thing as returning
BLOCK_REDIRECT_CODE.
General considerations
The call to the script is done in a synchronous manner. The calling code waits. There is a potential to the
encompassing business transaction to time out and be rolled back by Agiloft's application server if it takes too long.
If this is a frequent case, consider implementing asynchronous mode of operation on the caller side.
At this moment if a script replies on some 3rd party libraries not available in the Agiloft environment, these have to
be repackaged into the script archive.
The Agiloft .jar library files and their locations depend on your Agiloft version:
and
{Agiloft.installation.dir}/jboss
/server/sw/deploy
Main Library
Files agiloft-core.jar SWFunctionalities.jar
SW2Interfaces.jar
SWSeance-ejb.jar
Additional
Library Files commons*.jar commons*.jar
httpclient*.jar httpclient*.jar
xml*.jar xml*.jar
The Agiloft jar files you may need while developing are in { Agiloft.installation.dir}/wildfly/modules/system/layers
/base/com/agiloft/main/lib. In older versions of Agiloft, you can find them in { Agiloft.installation.dir}/jboss/server/sw
/lib/sw and { Agiloft.installation.dir}/jboss/server/sw/deploy.
Normally, the following classes are enough: SWFunctionalities.jar, SWSeance-ejb.jar. The jars that make the JBoss
environment would be under { Agiloft.installation.dir}/jboss/lib, { Agiloft.installation.dir}/jboss/server/lib.
Deployment considerations
Scripts must reside in the scripts directory. By default it is { Agiloft.installation.dir}/data/{kb.name}/scripts.
If your script consists of a single class, then you can simply drop the .class file in the scripts directory. This .class
must not belong to any package. If you have several classes, then you must put them in a jar file with a special key
in it's Manifest file:
Now your script name will be the name of the script .jar or .class file.
Agiloft runs on Java 6. As for JAXWS - the libraries available to the script will be those distributed with the
application server rather than those available from the runtime environment.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2007, <ac:structured-macro ac:name="companyname" ac:schema-
version="1" />, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.supportwizard.actions2.interfaces.ExternalScript;
import com.supportwizard.actions2.interfaces.ScriptActionException;
import com.supportwizard.actions2.interfaces.ScriptInput;
import com.supportwizard.actions2.interfaces.ScriptOutput;
import com.supportwizard.dictionary.SWChoiceLine;
import com.supportwizard.dictionary.appendtext.AppendOnlyTextContainer;
import com.supportwizard.dml.SWDataMap;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import java.io.IOException;
import java.io.InputStream;
/**
* The purpose of the "ReplicateTicket" is to automatically replicate
* Tickets from one server to another via REST call.
*
* This script is run in a script action from a rule invoked on create.
*
*/
public class ReplicateTicket implements ExternalScript {
private final static HttpClient httpClient;
static {
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
httpClient = new HttpClient(connectionManager);
}
public ScriptOutput runScript(ScriptInput input)
throws ScriptActionException {
//newRecord represents the Ticket that just have been created.
final SWDataMap newRecord = input.getNewRecord();
//PostMethod is used to invoke REST EWCreate operation
//on the target server
PostMethod method =
new PostMethod("http://server2.supportwizard.com:8080" +
"/ewws/EWCreate?$KB=Support&$login=script&" +
"$password=example&$lang=en&$table=case");
String additionalNotes = "";
The purpose of the ReplicateTicket is to automatically replicate Tickets from one server to another via a REST
call.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2007, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.supportwizard.actions2.interfaces.ExternalScript;
import com.supportwizard.actions2.interfaces.ScriptActionException;
import com.supportwizard.actions2.interfaces.ScriptInput;
import com.supportwizard.actions2.interfaces.ScriptOutput;
import com.supportwizard.datetime.convertor.EwDateTimeConvertor;
import com.supportwizard.dictionary.SWChoiceLine;
import com.supportwizard.dictionary.appendtext.AppendOnlyTextContainer;
import com.supportwizard.dml.SWDataMap;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Locale;
/**
* The purpose of the "CreateInEWSupportScript" is to automatically
* replicate Tickets from one server to another.
*
* This script is run in a script action from the rule invoked on create.
*
static {
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
httpClient = new HttpClient(connectionManager);
}
addParam("submitter_login",
(String) newRecord.get("submitter_login"), method);
addParam("Company_0", (String) newRecord.get("submitter_company"),
method);
addParam("customer_phone", (String) newRecord.get("phone"),
method);
The purpose of the CreateInEWSupportScript is to automatically replicate Tickets from one server to another.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2008, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.client.demo.*;
import java.net.URL;
/**
* The purpose of the "QueryAsset" is to demonstrate the current way of
* working with EnterpriseWizard SOAP Web Services.
*
* In particular the interface is generic - exposing and accepting parent
* object to allow one to use the same web service with different tables.
* Methods are low level - one has to combine several calls to achieve
* required. This is also a stateful version of the interface that relies
* on the underlying protocol support for stateful communication via
* cookies.
*
* This script is run from the commandline, accepts host, port, kbName,
* user, password and searchValue parameters.
*
* The script performs search by SQL on Asset_Table object of the
* knowledgebase, parses the returned object tree and prints results to the
* standard output.
*/
public class QueryAsset {
public static long[] queryAssets(
final CondeSoapBindingStub ewServiceAPI,
The purpose of the QueryAsset is to demonstrate the current way of working with Agiloft SOAP Web Services. In
particular the interface is generic - exposing and accepting parent object to allow one to use the same web service
with different tables. Methods are low level - one has to combine several calls to achieve required. This is also a
stateful version of the interface that relies on the underlying protocol support for stateful communication via cookies.
This script is run from the commandline, accepts host, port, kbName, user, password and searchValue parameters.
The script performs search by SQL on Asset_Table object of the knowledgebase, parses the returned object tree
and prints results to the standard output.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2008, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.client.demo.*;
import com.enterprisewizard.ws.client.ewsupport.WSAsset_Table;
import com.enterprisewizard.ws.api.EWServiceAPI;
import java.net.URL;
/**
* The purpose of the "QueryAssetNew" is to demonstrate the upcoming changes
* in EntepriseWizard Web Services interfaces, in particular "Single Method
* Operations", "Immediate Read", "Web Service Per Table" concepts.
*
* This script is run from the commandline, accepts host, port, kbName,
* user, password and searchValue parameters.
*
* The script performs search by SQL on Asset_Table object of the
* knowledgebase, parses the returned object tree and prints results to the
* standard output.
The purpose of the QueryAssetNew is to demonstrate the upcoming changes in EntepriseWizard Web Services
interfaces, in particular "Single Method Operations", "Immediate Read", "Web Service Per Table" concepts.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2008, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.client.demo.*;
import java.net.URL;
/**
* The purpose of the "CreateCaseCurrent" is to demonstrate the current way
* of working with <span class="product1">EnterpriseWizard</span> SOAP Web
Services.
*
* In particular the interface is generic - exposing and accepting parent
* object to allow one to use the same web service with different tables.
* This is also a stateful version of the interface that relies on the
* underlying protocol support for stateful communication via cookies.
*
* This script is run from the commandline, accepts host, port, kbName,
* user, password and several specific field parameters.
*
* The script invokes EWCreate from SOAP API to create a Case in EW.
*/
public class CreateCaseCurrent {
public static long createTicket(final String assetDeviceName,
final int severity,
final String summary,
final Integer serverSerial,
final String agent,
final String ownerUID,
final CondeSoapBindingStub ewServiceAPI)
throws Exception {
The purpose of the CreateCaseCurrent is to demonstrate the current way of working with Agiloft SOAP Web
Services. In particular the interface is generic - exposing and accepting parent object to allow one to use the same
web service with different tables. This is also a stateful version of the interface that relies on the underlying protocol
support for stateful communication via cookies. This script is run from the commandline, accepts host, port,
kbName, user, password and several specific field parameters. The script invokes EWCreate from SOAP API to
create a Case in Agiloft.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2008, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.client.demoonecall.EWServiceAPI;
The purpose of the CloneTicketNew is to demonstrate the way to pass results of one WS method to another and
challenges associated with this. The script uses Single Method Operations, Immediate Read calls that will be
available in upcoming release of Agiloft Web Services. This script is run from the commandline, accepts host, port,
kbName, user, password and searchValue parameters. The script performs search by SQL on Case object of the
knowledgebase and submits the returned object right back in to create a clone.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2007, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.api.EWWSBaseUserObjectMap;
import com.enterprisewizard.ws.impl.EWSimpleImplHelperWrapper;
import com.enterprisewizard.ws.utils.LogHelper;
import com.supportwizard.actions2.interfaces.ExternalScript;
import com.supportwizard.actions2.interfaces.ScriptActionException;
import com.supportwizard.actions2.interfaces.ScriptInput;
import com.supportwizard.actions2.interfaces.ScriptOutput;
import com.supportwizard.dml.SWDataMap;
import com.supportwizard.dml.SWRecordPK;
import com.supportwizard.seance.Seance;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.InputStream;
/**
* The purpose of the "POSTPush" is to submit the result of modification of
* a record in <span class="product1">EW</span> to an external host via HTTP
POST.
* This script is run in a script action from the rule invoked on update.
The purpose of the "POSTPush" is to submit the result of modification of a record in Agiloft to an external host via
HTTP POST. This script is run in a script action from the rule invoked on update. The script re-uses parts of WS
/REST internal code to convert internal datatypes into POST parameters.
package com.enterprisewizard.examples;
/*
* (c) Copyright 2008, EnterpriseWizard, Inc. All Rights Reserved.
* No part of this document may be stored in a retrievable system,
* transmitted or reproduced in any way without the prior written
* permission of EnterpriseWizard.
*/
import com.enterprisewizard.ws.utils.LogHelper;
import com.enterprisewizard.scriptactions.conde.TicketUpdateScript;
import com.micromuse.response.server.soapserver.*;
import com.supportwizard.actions2.interfaces.ExternalScript;
import com.supportwizard.actions2.interfaces.ScriptActionException;
import com.supportwizard.actions2.interfaces.ScriptInput;
import com.supportwizard.actions2.interfaces.ScriptOutput;
import com.supportwizard.dml.SWDataMap;
import com.supportwizard.dml.SWRecordPK;
import com.supportwizard.seance.Seance;
import org.apache.log4j.Logger;
import java.net.URL;
/**
* The purpose of the "NetcoolTicketUpdateScript" is to invoke a policy in
* IBM Tivoli Netcool when a ticket is closed in EnterpiseWizard.
* A policy can be invoked by calling a "Listener" Web Service provided
* with each instance of Netcool.
* This script is run in a script action from the rule invoked on update
* and also periodically every 5 minutes. The purpose of the periodic run
* is to pick up those tickets for which for some reason the information
* was not transmitted successfully i.e. retry if some failure has
* occured.
* The script uses an integer field "submitted_to_netcool" to indicate that
* the invocation was successful. To avoid cluttering history by recording
* the success on the very first run the the suggested pattern is to have
* the field set to 1 (success) by default and set value to 0 only when it
* fails.
The purpose of the NetcoolTicketUpdateScript is to invoke a policy in IBM Tivoli Netcool when a ticket is
closed in EnterpiseWizard. A policy can be invoked by calling a Listener web service provided with each instance of
Netcool. This script is run in a script action from the rule invoked on update and also periodically every 5 minutes.
The purpose of the periodic run is to pick up those tickets for which for some reason the information was not
transmitted successfully i.e. retry if some failure has occurred. The script uses an integer field
submitted_to_netcool to indicate that the invocation was successful. To avoid cluttering history by recording
the success on the very first run the the suggested pattern is to have the field set to 1 - success - by default and set
value to 0 only when it fails. The script will only update the field if a change is required. To attempt a resend such
records can be isolated later by a rule run periodically with an IF/THEN condition based on Advanced Filter - the
field changed value from 1 to 0 in the last X minutes.
Python Scripts
© 2022 Agiloft Inc. 366
Python Scripts
The Python API makes it easy to write custom scripts. It consists of two modules, ALget.py and ALset.py, that
are used for reading input files and writing output files.
There are several ready-made functions available, but many of the scripts only use the following 6 functions:
ALget(filename)
ALget.value()
ALset.setRecordField
ALset.setMessage
ALset.setExitAction
ALset.save
Mastering the functions listed above will help you to write very powerful scripts. For more details on the ALget and
ALset functions, visit the wiki articles entitled ALget Common Use and ALset Common Use. You can also find
valuable information for writing scripts on the Script Structure and Basics page, which will help you include all
required functions.
In addition, the script may be able to use "old_state", which provides the old field values of the records. These
are stored in an array that is ordered the same way as the current states, so the old values for the first current state
record can be obtained with a similar function and list value, ALget.oldRecordNamesList()[0]. These old
values are the values before the current edit of the records, assuming the script is being triggered by an edit to a
record. These old values are provided so you can easily see, check, and validate user changes to fields and values.
recordName
Many of the functions take a parameter recordName as an input variable. If recordName is omitted from the input
variable, the first of the current state records is used by default, and then is used to populate the output file.
The discussion here assumes that “get_data” has been defined in the main function as follows:
get_data = ALget(inputXMLFile)
Retrieving Values
The specific examples call functions of “get_data” to retrieve values. Many types of values can be retrieved from the
input file.
Example
# Get the value in the 'type_of_problem' field. This uses the name of the field,
not the label.
Value1 = get_data.value('type_of_problem')
Example
# Get information about attached files. The files are retrieved as a list of
objects with attributes for name and path.
# These are the only attributes available.
Example
Example
# Retrieve information about the 'assignee' field, which is a link to both the
"teams" and "contacts" tables. This creates a list of objects.
# Then check if the 'assignee' field has a team selected instead of a contact.
Value6 = get_data.linkedRecordsNamesList("assigned_team")
if get_data.tableNameForRecord(Value6[0]) == "teams":...
Objects Returned
In addition to integer and text values, some ALget functions return special objects.
ALAttachedFile
This object presents an attached file with its file name (name) and location (path).
ALMultipleLFContainer
This object represents multiple imported values for a linked field. There are several available functions for this
object type.
findByValue(value): Input a value to search for and receive a [recordID, value] pair if it exists, or None
otherwise.
containsValue(value): Input a value to search for and receive True if the object contains any lines with
the value.
items(): Receive a list of all [recordID, value] pairs in the container.
itemsMap(): Receive a dict of { donorRecordId: value, ..., donorRecordX: valueX } for the object.
The discussion here assume that “set_data” has been defined in the main function as follows:
set_data = ALset(outputXMLFile)
Setting Values
The specific examples call functions of “set_data” to set values. Many types of values can be set.
Example
Syntax:
Example
#Replace the file "old.txt" in the "attached_files" field with the file "new.txt"
located at C:\tmp. Note that this is only used with file versioning.
replaceFile('attached_files', 'old.txt', 'new.txt', 'C:\tmp\')
Syntax: message(message)
Example
Syntax: redirect(value)
Example
An exit action
The exitAction function determines whether the record is saved or rolled back, and whether the user is
redirected. Set the value parameter to one of the available exit codes:
Example
#Set the exit action. The only accepted actions are the 4 shown.
set_data.exitAction(0)
set_data.exitAction(3)
set_data.exitAction(1)
set_data.exitAction(2)
Syntax: save(fileName)
Example
#Save the output file. This is necessary to make sure the changes are actually
conveyed back to the system.
set_data.save()
Example
#####################################################################
# Synopsis:
# Store the CPU load in a record of the KB and alert the sysadmin
# if the load is higher than a threshold.
######################################################################
import sys
from ALget import ALget
from ALset import ALset
from ALperf import ALperf
import smtplib
from email.mime.text import MIMEText
action(sys.argv[1], sys.argv[2])
hostName
ipAddress
systemBoot
osType
osDistro
osBuild
gLibc
cpuCount
ramTotal
ramUsed
memAvailable
swapTotal
swapUsed
databaseType
databaseAddress
jbossType
jbossMem
mysqlMem
cpuLoad
topCpuProcess
topMemProcess
appDiskUsed
appDiskFree
dbDiskUsed
dbDiskFree
baseDir
getCpuModel()
getJreVersion()
getNginxVersion()
getAppBootTime()
getHeapUsage()
You must add these functions to every Python script used in Agiloft in order for the script to terminate
properly:
out.exitAction(ALset.ACCEPT)
out.save()
Samples
Click a sample below to see the script text.
######################################################################
# Synopsis:
#
# Redirect the user after he/she submits the ticket.
#
# There is no need to specify the Python interpreter if the script is named *.py
# because the system recognizes .py scripts as being Python based and will
# use the Python that is included in the distribution.
#
# If you use this script with your own copy of Python, please note that
# it requires ElementTree module to be installed
#
######################################################################
import sys
from ALget import ALget
from ALset import ALset
def action(inputXMLFile, outputXMLFile):
input = ALget(inputXMLFile)
out = ALset(outputXMLFile)
######################################################################
#
# Only log the user out if he/she is not a member of the Staff
# group. In other words, we want all members of staff to be able
# to log tickets on behalf of users without being logged out.
######################################################################
groups = input.globalVariable('my_groups')
groupAllowedToClose = "Staff"
if groupAllowedToClose not in groups.split(","):
out.redirect("http://www.example.com")
#####################################################################
# Synopsis:
# Send new users a confirmation email with their login and password
#
# There is no need to specify the Python interpreter if the script is named *.py
# because the system recognizes .py scripts as being Python based and will
# use the Python that is included in the distribution.
#
# If you use this script with your own copy of Python, please note that
# it requires ElementTree module to be installed
#
######################################################################
import sys
from ALget import ALget
from ALset import ALset
import smtplib
from email.mime.text import MIMEText
######################################################################
# Only send email to members of the Customer group.
# For security reasons, it is unsafe to send password information in
# plain text to privileged groups such as staff or admin.
######################################################################
def action(inputXMLFile, outputXMLFile):
input = ALget(inputXMLFile)
out = ALset(outputXMLFile)
groups = input.valueMultipleLF("f_group")
me = "[email protected]"
you = input.value("email")
if groups and groups.containsValue("Customer") and you:
body = ("Dear " + input.value("full_name") + ",\n"
"Thank you for registering with our support system.\n\n"
"You have chosen the following\n"
"login: " + input.value("_login") + "\n"
"password: " + input.value("password") + "\n"
"Thank you,\n"
"CompanyName Support staff\n")
msg = MIMEText(body)
msg['Subject'] = "Welcome to CompanyName Support"
msg['From'] = me
msg['To'] = input.value("email")
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()
out.exitAction(ALset.ACCEPT)
############################################################################
# Synopsis:
# Check if the user belongs to QA group and block closure of the ticket if
# he/she doesn't.
#
# There is no need to specify the Python interpreter if the script is named *.py
# because the system recognizes .py scripts as being Python based and will
# use the Python that is included in the distribution.
#
# If you use this script with your own copy of Python, please note that
# it requires ElementTree module to be installed
############################################################################
import sys
from ALget import ALget
from ALset import ALset
def action(inputXMLFile, outputXMLFile):
input = ALget(inputXMLFile)
out = ALset(outputXMLFile)
groups = input.globalVariable('my_groups')
groupAllowedToClose = "QA"
if groupAllowedToClose not in groups.split(",") and input.value("wfstate")
== "Closed" and input.value("wfstate", input.oldRecordsNamesList()[0]) !=
"Closed":
# Set a field value. In this case, we set the status field
# to its old value
out.recordField("wfstate", input.value("wfstate", input.
oldRecordsNamesList()[0]))
print("Restore state")
out.exitAction(ALset.ACCEPT)
out.save()
action(sys.argv[1], sys.argv[2])
######################################################################
# Synopsis:
# This script performs some common tasks typical of a script.
# You could run this script against the input file example:
# filename.py input.xml output.xml
#
# There is no need to specify the Python interpreter if the script is named *.py
# because the system recognizes .py scripts as being Python based and will
# use the Python that is included in the distribution.
#
# If you use this script with your own copy of Python, please note that
# it requires ElementTree module to be installed
#
######################################################################
To use the ALget and ALset functions they must be imported from the modules. All of the Agiloft sample scripts
start with importing just these functions. The standard sys module should also be imported.
import sys
# interface modules to interact with input and output xml files
from ALget import ALget
from ALset import ALset
The rest of the script can consist of a single function unless other references are needed. The Agiloft sample
scripts define a single function called 'action' and calls that function at the end.
The Agiloft python scripts are always called with 2 arguments: an XML file representing the current and former
record states, and a second XML file for the version of the records which should be saved to the database when the
script is complete.
The following lines are required to generate XML at the end of the script:
out.exitAction(ALset.ACCEPT)
out.save()
A normal Agiloft python script involves reading values, history, and user data from the input file and saving any
changes to the output file.
The first step in dealing with these files is to load them into Python objects that can be later referenced.
The sample scripts and examples assume the scripts are structured this way and refer to “input” and “out” when
using ALget and ALset.
Replace "Demo" with the actual KB name before executing it through the Admin Console at Setup > Debugging >
Bean shell.
Note: If demo records are deleted using the Admin Console delete demo data functions, the "deleted" records leave
a "null" record in the SQL DB, and this will block any attempt to reset ID numbers or to import directly to that ID
number. Those empty SQL records can be deleted by support or developers.
sql.dbConnect();
try {
for
(SWTable table :
tableHome.findByProjectAndType(projectHome.findByName(projectName).getSWProjectID(),
SWTable.USER_OBJECT)) {
String autoincColumnDBName = null;
for (SWColumn column : table.getSWColumns()) {
if (column.isAutoIncrement()) {
autoincColumnDBName = column.getDBName();
break;
}
}
if (autoincColumnDBName == null) continue;
ResultSet
rs = sql.getConnection().createStatement()
.executeQuery("select max("+autoincColumnDBName+") from "+table.getDBName());
int autoinc = 0;
if (rs.next()) autoinc = rs.getInt(1);
autoinc++;
log.append("setting autoinc="+autoinc+" for table="+table.getDBName()+"."
+autoincColumnDBName+"\n");
if (DBSyntaxHelper.isMSSQL)
sql.getConnection().createStatement().execute("DBCC CHECKIDENT ('[" + table.
getDBName() + "]', RESEED, " + autoinc + ")");
else