This content applies solely to Appian RPA, which must be purchased separately from the Appian base platform. |
The following content is based on the previous version of the Robotic Process Designer.
The main purpose of this tutorial is to show you an example of using and create the Appian RPA process queues. Our robot searches in Google the terms included in an Excel file and retrieves the data contained in the first result of the search.
We will explain the configuration of the robot, as well as the creation of the queue. We'll also show you how to load items on a queue.
You can get the source code and additional files in the following links:
Description | Link |
---|---|
Source Code | robot-tutorial-queues.zip |
Items File Example | robotQueuesInput.xlsx |
Chrome Driver v2.35 | chromedriver.exe |
Review Browser module architecture page for other browser versions.
You must import the Eclipse project included in the ZIP file attached at the beginning of this tutorial. After
importing the project, it is very important to modify the pom.xml file. You have to fill in the <parent>
tag with the Appian RPA version you are using, and change inside the <profile>
tag the
<url>
and <repository>
tags with the ones corresponding to the repository of our installation.
The robot can create the queue automatically or connect to an already created queue. If an input file exists, the queue will be created automatically. In this section, we will configure a new queue that will manage the items to process with the following characteristics:
Now, we will load the items to process into the queue using an Excel file with the following fields:
You can download an example of this file at the beginning of this tutorial.
To load the queue with the contents of the file, we must click the
icon , located in the upper hand-right
corner of the screen, and select the file to load. Once loaded, we can
check that the items are available and pending to process.
The robot Workflow contains two type of actions, those related to the interaction with the queue and those related with the item to process.
The tasks to perform, once the item is retrieved, will be:
Concerning the interaction with the queue, the robot will perform the following tasks:
We will create a new robot having the following workflow:
It is necessary that both the robot and the queue have, at least, one common permission.
This robot will use the Chrome browser, therefore the bridge service chromeWebDriver is needed as a support file. Appian RPA also allows to add support files globally, making them available to several robots through permissions, so no local support files are needed. For more information about global support files, you can check the Console User's Guide.
We will analyze the methods implemented in the Robot of queues. We will differentiate the methods used to work with the queue and those used to process the item in google.
First, we will show the definition of the variable, where the most important elements are the instances of IQueueManager, IQueue and IQueueItem.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* Robot Queues Tutorial.
*
* @author jidoka
*/
@Robot
public class RobotQueuesTutorial implements IRobot {
/** Server. */
private IJidokaServer<?> server;
/** Windows module. */
@SuppressWarnings("unused")
private IWindows windows;
/** The qmanager. */
private IQueueManager qmanager;
/** The queue commons. */
private QueueCommons queueCommons;
/** The queue ID. */
private String queueID;
/** Web Browser Support. */
private IWebBrowserSupport browser;
/** The google. */
private GoogleSearchCommons google;
/** Browser parameter. */
private static final String PARAM_BROWSER = "Browser";
/** Input file parameter name. */
private static final String PARAM_INPUT_FILE = "inputFile";
/** First row. */
private static final int FIRST_ROW = 0;
/** The current queue. */
private IQueue currentQueue;
/** The current item. */
private GoogleSearchRow currentItem;
/** The current item index. */
private int currentItemIndex = 1;
/** The current item queue. */
private IQueueItem currentItemQueue;
/** The input excel file. */
private String inputExcelFile;
/** The output excel file. */
private String outputExcelFile;
/** Is manual queue */
private boolean manualQueue;
The start action will be in charge of initializing the elements defined above, obtaining the parameters and checking if the queue has been created manually. The robot will check if there is an input file, the queue will be created automatically.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@override
public boolean startUp() throws Exception {
// Get the modules instance.
server = (IJidokaServer<?>) JidokaFactory.getServer();
browser = IWebBrowserSupport.getInstance(this);
return IRobot.super.startUp();
}
/**
* Action "start".
*
* This method is used to Initialize the Jidoka modules instances
*
* @throws Exception the exception
*/
public void start() {
try {
// Initialize the QueueManager
qmanager = server.getQueueManager();
String fileName = server.getWorkflowParameters().get(PARAM_INPUT_FILE);
// No manual queue
if (StringUtils.isNotBlank(fileName)) {
inputExcelFile = Paths.get(server.getCurrentDir(), fileName).toString();
manualQueue = false;
} else {
manualQueue = true;
}
queueCommons = new QueueCommons();
queueCommons.init(qmanager);
// Open the browser
openBrowser();
google = new GoogleSearchCommons();
google.init(server, browser);
} catch (Exception e) {
throw new JidokaFatalException("Error initializing the robot");
}
}
The first method launched after initialization is selectQueue. This method obtains the reference of the queue that contains the items to be processed if the queue has been created manually, or create it automatically if it has not been created previously. The main method is shown below, and you can see that after obtaining the reference or create the queue, it is possible to retrieve the pending items.
Also it is included the code of the method getQueueFromId(), used to retrieve the IQueue instance from IQueueManager object. The method used is assignQueue().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* Select queue.
*
* @throws Exception the exception
*/
public void selectQueue() {
try {
if (manualQueue) {
if (StringUtils.isNotBlank(qmanager.preselectedQueue())) {
queueID = qmanager.preselectedQueue();
server.info("Selected queue ID: " + queueID);
currentQueue = queueCommons.getQueueFromId(queueID);
}
} else {
queueID = queueCommons.createQueue(inputExcelFile);
server.info("Queue ID: " + queueID);
addItemsToQueue();
currentQueue = queueCommons.getQueueFromId(queueID);
}
if (currentQueue == null) {
server.debug("Queue not found");
return;
}
server.setNumberOfItems(currentQueue.pendingItems());
} catch (Exception e) {
throw new JidokaFatalException("Error selecting the queue");
}
}
/**
* Create Queue
*
* @param excelFilePath Excel file
* @return Queue id
* @throws IOException Signals that an I/O exception has occurred.
* @throws JidokaQueueException the jidoka queue exception
* @throws JidokaException the jidoka exception
*/
public String createQueue(String excelFilePath) throws IOException, JidokaQueueException, JidokaException {
CreateQueueParameters qParam = new CreateQueueParameters();
// Gets the input file
File fileInput = Paths.get(excelFilePath).toFile();
String fileName = fileInput.getName();
// Set the queue parameters
qParam.setDescription("Queue created from file:" + fileName + " on " + new Date().toString());
qParam.setFileName(fileName);
qParam.setName(fileName);
qParam.setPriority(EPriority.HIGH);
qParam.setAttemptsByDefault(1);
try {
qParam.setFileContent(FileUtil.readBytes(fileInput));
} catch (IOException e) {
throw new JidokaException("Error reading fileInput: " + e.getMessage(), e);
}
String createdQueueId = qmanager.createQueue(qParam);
return createdQueueId;
}
/**
* Gets the queue from id.
*
* @param queueId the queue id
* @return the queue from id
* @throws JidokaQueueException the jidoka queue exception
*/
public IQueue getQueueFromId(String queueId) throws JidokaQueueException {
try {
AssignQueueParameters qqp = new AssignQueueParameters();
qqp.queueId(queueId);
IQueue queue = qmanager.assignQueue(qqp);
return queue;
} catch (IOException e) {
throw new JidokaQueueException(e);
}
}
The following two methods create the queue and add the items programmatically (when the queue has not been created manually) configuring its different attributes, such as name, priority or description, in addition, also adds the name and content of the file from which the items will be added.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* Adds the items to queue.
*
* @throws Exception the exception
*/
private void addItemsToQueue() throws Exception {
IJidokaExcelDataProvider<GoogleSearchRow> dataProvider = IJidokaDataProvider.getInstance(this, Provider.EXCEL);
dataProvider.init(inputExcelFile, null, FIRST_ROW, new GoogleSearchRowMapper());
try {
// Get the next row, each row is a item
while (dataProvider.nextRow()) {
GoogleSearchRow googleSearchRow = dataProvider.getCurrentItem();
CreateItemParameters itemParameters = new CreateItemParameters();
// Set the item parameters
itemParameters.setKey(googleSearchRow.getSearchTerm());
itemParameters.setPriority(EPriority.NORMAL);
itemParameters.setQueueId(queueID);
itemParameters.setReference(String.valueOf(dataProvider.getCurrentItemNumber()));
Map<String, String> functionalData = new HashMap<>();
functionalData.put(GoogleSearchRowMapper.SEARCH_HEADER, googleSearchRow.getSearchTerm());
functionalData.put(GoogleSearchRowMapper.TITLE_HEADER, googleSearchRow.getTitle());
functionalData.put(GoogleSearchRowMapper.URL_HEADER, googleSearchRow.getUrl());
functionalData.put(GoogleSearchRowMapper.DESCRIPTION_HEADER, googleSearchRow.getDescription());
itemParameters.setFunctionalData(functionalData);
qmanager.createItem(itemParameters);
server.debug(String.format("Added item to queue %s with id %s", itemParameters.getQueueId(),
itemParameters.getKey()));
}
} catch (Exception e) {
throw new JidokaQueueException(e);
} finally {
try {
// Close the excel file
dataProvider.close();
} catch (IOException e) {
throw new JidokaQueueException(e);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Adds the items to queue.
*
* @throws Exception the exception
*/
private void addItemsToQueue() throws Exception {
IJidokaExcelDataProvider<GoogleSearchRow> dataProvider = IJidokaDataProvider.getInstance(this, Provider.EXCEL);
dataProvider.init(inputExcelFile, null, FIRST_ROW, new GoogleSearchRowMapper());
try {
// Get the next row, each row is a item
while (dataProvider.nextRow()) {
GoogleSearchRow googleSearchRow = dataProvider.getCurrentItem();
CreateItemParameters itemParameters = new CreateItemParameters();
// Set the item parameters
itemParameters.setKey(googleSearchRow.getSearchTerm());
itemParameters.setPriority(EPriority.NORMAL);
itemParameters.setQueueId(queueID);
itemParameters.setReference(String.valueOf(dataProvider.getCurrentItemNumber()));
Map<String, String> functionalData = new HashMap<>();
functionalData.put(GoogleSearchRowMapper.SEARCH_HEADER, googleSearchRow.getSearchTerm());
functionalData.put(GoogleSearchRowMapper.TITLE_HEADER, googleSearchRow.getTitle());
functionalData.put(GoogleSearchRowMapper.URL_HEADER, googleSearchRow.getUrl());
functionalData.put(GoogleSearchRowMapper.DESCRIPTION_HEADER, googleSearchRow.getDescription());
itemParameters.setFunctionalData(functionalData);
qmanager.createItem(itemParameters);
server.debug(String.format("Added item to queue %s with id %s", itemParameters.getQueueId(), itemParameters.getKey()));
}
} catch (Exception e) {
throw new JidokaQueueException(e);
} finally {
try {
// Close the excel file
dataProvider.close();
} catch (IOException e) {
throw new JidokaQueueException(e);
}
}
}
The next method to execute is hasMoreItems(). This function will recover the elements of the queue until there are none left, Code of hasMoreItems() and getNextItem() is shown below. This second method is responsible for asking the server for the following item to process by calling the reserveItem() function. Another interesting point in this code is the way to retrieve the item fields, using the function* currentItemQueue.functionalData().get("TO SEARCH").
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Checks for more items.
*
* @return the string
* @throws Exception the exception
*/
public String hasMoreItems() throws Exception {
// retrieve the next item in the queue
currentItemQueue = queueCommons.getNextItem(currentQueue);
if (currentItemQueue != null) {
// Get the item
currentItem = new GoogleSearchRow();
currentItem.setSearchTerm(currentItemQueue.functionalData().get("TO SEARCH"));
server.debug(currentItemQueue.functionalData());
// set the stats for the current item
server.setCurrentItem(currentItemIndex++, currentItem.getSearchTerm());
return "yes";
}
currentItem = null;
return "no";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Gets the next item.
*
* @return the next item
* @throws JidokaQueueException the jidoka queue exception
*/
public IQueueItem getNextItem(IQueue currentQueue) throws JidokaQueueException {
try {
if (currentQueue == null) {
return null;
}
return qmanager.reserveItem(reserveItemsParameters);
} catch (JidokaQueueException e) {
throw e;
} catch (Exception e) {
throw new JidokaQueueException(e);
}
}
The next three methods perform the Google search and get the data for the first result. The methods are openGoogle(), googleSearch() and extractResultData().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* Open google home page.
*/
public void openGoogle() {
try {
// Open the google home page
google.openGoogle();
} catch (Exception e) {
throw new JidokaFatalException("Error opening Google");
}
}
/**
* Google search.
*/
public void googleSeach() {
try {
// Search for the term
google.search(currentItem.getSearchTerm());
// Send screen capture
server.sendScreen("google search");
} catch (Exception e) {
throw new JidokaItemException("Error searching in Google");
}
}
/**
* Extract result data.
*/
public void extractResultData() {
try {
// extract the result data for the WebElement
GoogleSearchResultWebElement r = google.getResultWebElement();
currentItem.setTitle(r.getTitle());
currentItem.setUrl(r.getUrl());
currentItem.setDescription(r.getDescription());
server.debug("*******************************************************");
server.debug(currentItem.getTitle());
server.debug(currentItem.getUrl());
server.debug(currentItem.getDescription());
server.debug("*******************************************************");
} catch (Exception e) {
throw new JidokaItemException("Error extracting the result data");
}
}
Next, we will connect with the queue to modify the data of the item with the result. After the modification, the element will be unlocked and finished. The steps to perform these actions are:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Update item queue.
*
* @throws JidokaQueueException the Jidoka queue exception
*/
public void updateItemQueue() throws JidokaQueueException {
try {
// Override the functional data in the queue item
Map<String, String> funcData = currentItemQueue.functionalData();
funcData.put(GoogleSearchRowMapper.SEARCH_HEADER, currentItem.getSearchTerm());
funcData.put(GoogleSearchRowMapper.TITLE_HEADER, currentItem.getTitle());
funcData.put(GoogleSearchRowMapper.URL_HEADER, currentItem.getUrl());
funcData.put(GoogleSearchRowMapper.DESCRIPTION_HEADER, currentItem.getDescription());
// Release the item. The queue item result will be the same as the currenItem
ReleaseItemWithOptionalParameters rip = new ReleaseItemWithOptionalParameters();
rip.functionalData(funcData);
// Is mandatory to set the current item result before releasing the queue item
server.setCurrentItemResultToOK(currentItem.getTitle());
qmanager.releaseItem(rip);
} catch (Exception e) {
throw new JidokaItemException("Error updating the queue item");
}
}
When the robot has no more elements to process, before finishing, it should check if it is the last instance in execution or if there are other instances running. If this is the last process, it will create the Excel output file including the result of all items. It will also close the queue.
To avoid simultaneous access, the first action to be performed is to block access to the queue with the reserveQueuePaged() method. This allows you to be sure that no other instance will modify the status of the queue. With an exclusive access to the queue, we check if there is any item left. If there are elements, the robot ends. Another instance will close the queue. To unblock the access we execute the releaseQueue() method without changing the queue status.
If there are no pending item to process, we execute the releaseQueue action, but in this case we change the status of the queue to CLOSED.
Finally, the last execution will group the elements processed by all instances and generate the Excel file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Close queue action
*
* @return the string
* @throws IOException Signals that an I/O exception has occurred.
* @throws JidokaQueueException the Jidoka queue exception
*/
public void closeQueue() throws IOException, JidokaQueueException {
try {
// First we reserve the queue (other robots can't reserve the queue at the same
// time)
ReserveQueuePagedParameters rqp = new ReserveQueuePagedParameters();
rqp.setQueueId(currentQueue.queueId());
IReservedQueuePaged reservedQueue = qmanager.reserveQueuePaged(rqp);
// Robot can't reserver the current queue
if (reservedQueue == null) {
server.debug("Can't reserve the queue with ID: " + currentQueue.queueId());
return;
}
// Check for pending items.
int pendingItems = reservedQueue.queue().pendingItems();
server.info(String.format("The queue has %s pending items", pendingItems));
if (pendingItems > 0) {
// Release the queue without closing
releaseQP.closed(false);
qmanager.releaseQueue(releaseQP);
server.info("Queue released without closing");
return;
}
// Update the queue input file and save it
updateQueueOutputFile(qmanager, currentQueue.queueId());
// Release the queue closing it
releaseQP.closed(true);
server.info("Queue closed and released");
qmanager.releaseQueue(releaseQP);
} catch (Exception e) {
throw new JidokaFatalException("Error closing the queue");
}
}
The last method we will analyze is updateQueueOutputFile(). In this method we recover the file used to upload the elements using IReservedQueueue.queue().fileContent(). We also retrieve the list of items from the queue (in a loop) with the modified state. Both methods are launched with exclusive access to the queue.
In this tutorial, we have filled in a copy of the original file with the result of the execution. We use the IJidokaExcelDataProvider module to perform this task
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
*
* Update queue output file. This method extracts the file loaded in the indicated
* queue and updates each row with the data stored in the items.
*
* @throws JidokaQueueException
* @throws IOException
*/
private void updateQueueOutputFile(QueueManager qmanager, String queueId) throws JidokaQueueException, IOException {
Path resFolderPath = Paths.get(server.getCurrentDir(), "res");
if (!resFolderPath.toFile().exists()) {
resFolderPath.toFile().mkdirs();
}
File file = resFolderPath.resolve(rq.queue().fileName()).toFile();
if (file.exists()) {
Files.delete(file.toPath());
}
try {
// Retrieve the file from the Queue and store it in a File
FileUtils.writeByteArrayToFile(file, rq.queue().fileContent());
} catch (IOException e) {
throw new JidokaQueueException(e);
}
IJidokaExcelDataProvider<GoogleSearchRow> dpRes = IJidokaDataProvider.getInstance(this, Provider.EXCEL);
try {
// Data provider initialize using the Excel file
dpRes.init(file.getAbsolutePath(), null, 0, new GoogleSearchRowMapper());
} catch (Exception e) {
throw new JidokaQueueException(e);
}
try {
int page = 0;
do {
ReserveQueuePagedParameters rqp = new ReserveQueuePagedParameters()
.queueId(queueId)
.page(page++);
IReservedQueuePaged rq = qmanager.reserveQueuePaged(rqp);
List<IQueueItem> items = rq.items();
// Now we fill all the rows in the excel with the item data.
while (dpRes.nextRow()) {
IQueueItem queueItem = items.stream()
.filter(i -> i.reference().equals(String.valueOf(dpRes.getCurrentItemNumber()))).findFirst()
.orElse(null);
if (queueItem == null) {
continue;
}
server.debug("Updating item: " + queueItem.key());
Map<String, String> map = queueItem.functionalData();
GoogleSearchRow currentRow = new GoogleSearchRow();
currentRow.setSearchTerm(map.get(GoogleSearchRowMapper.SEARCH_HEADER));
currentRow.setTitle(map.get(GoogleSearchRowMapper.TITLE_HEADER));
currentRow.setUrl(map.get(GoogleSearchRowMapper.URL_HEADER));
currentRow.setDescription(map.get(GoogleSearchRowMapper.DESCRIPTION_HEADER));
dpRes.updateItem(currentRow);
}
} while (rq.moreItemsAvailable());
} catch (Exception e) {
throw new JidokaQueueException(e);
} finally {
try {
// Close the excel file
dpRes.close();
} catch (IOException e) {
throw new JidokaQueueException(e);
}
}
outputExcelFile = file.getAbsolutePath();
server.info("Output excel file stored in: " + outputExcelFile);
}
Once the robot is implemented, it will be deployed into the Appian RPA repository.
mvn clean deploy
In this example, we will launch the robot from the queue itself, therefore, the queue must have been created manually beforehand. Executing two instances of the robot, therefore, it will be necessary to have two resources with the appropriate privileges. This tutorial can be started with one resource, but with this configuration this resource will process all the elements.
Two executions will start simultaneously.
When the robots finish, you can check that the queue status is closed.
You can consult the result in the details of the queue, downloading the excel file.