Skip to main content
Version: 10.2.8

Utilities

Work with File Storage

ODF introduces the com.workfusion.rpa.core.plugin.s3.S3PluginAdapter interface for S3 operations.

Example of File Storage usage
Map<String, String> getCommonS3Attributes();

<R> R executeWithCustomSettings(Supplier<R> func, Map<String, String> customS3Attributes);

byte[] get(String url);

List<String> listEntries(String pattern, ZonedDateTime minDate, ZonedDateTime maxDate, String prefix);

List<String> listEntries(String prefix);

void delete(String url);

S3ResultItem put(byte[] data, String s3key, CannedAccessControlList acl, String contentType, String contentDisposition, Long expiresInSeconds);

S3ResultItem put(byte[] data, String s3key);

void copy(String from, String to, CannedAccessControlList acl);

Here, you can find S3 API usage examples.

S3PluginAdapter can be obtained from PluginAdapterFactory in the following way:

S3PluginAdapter s3PluginAdapter = pluginAdapterFactory.getPluginAdapter(PluginAdapterFactory.PluginsEnum.S3);

Be aware that S3PluginAdapter requires a bucket attribute to be set:

S3PluginAdapter s3PluginAdapter = pluginAdapterFactory.getPluginAdapter(PluginAdapterFactory.PluginsEnum.S3);
s3PluginAdapter.getCommonS3Attributes().put(S3PluginWebharvestAdapter.ATTRIBUTE_BUCKET, "doc-upload");
Example of Module with S3PluginAdapter usage
public class ExampleModuleWithS3 implements Module {

@Provides
public S3PluginAdapter s3PluginAdapter(PluginAdapterFactory pluginAdapterFactory) {
S3PluginAdapter s3PluginAdapter = pluginAdapterFactory.getPluginAdapter(PluginAdapterFactory.PluginsEnum.S3);
s3PluginAdapter.getCommonS3Attributes().put(S3PluginWebharvestAdapter.ATTRIBUTE_BUCKET, "doc-upload");
return s3PluginAdapter;
}

}

public class ExampleTransactionWithS3 implements TransactionSupplier {

private final S3PluginAdapter s3PluginAdapter;

@Inject
public ExampleTransactionWithS3(S3PluginAdapter s3PluginAdapter) {
this.s3PluginAdapter = s3PluginAdapter;
}

String uploadDocument(String documentContent) {
byte[] data = documentContent.getBytes(StandardCharsets.UTF_8);
String s3Key = randomFileName() + ".txt";
return s3PluginAdapter.put(data, s3Key).getDirectUrl();
}

}

Work with Data Stores

The com.workfusion.rpa.core.plugin.datastore.DatastorePluginAdapter interface is used for Data Store operations.

Example of Data Store usage
List<DbRowVariable> selectQuery(String datastoreName, String query, Integer maxRows);

List<DbRowVariable> selectQuery(String datastoreName, String query);

Variable executeQuery(String datastoreName, String query, Integer maxRows);

Variable executeQuery(String datastoreName, String query);

void createDatastore(String datastoreName, Map<String, DataStoreColumnType> columnsMap, List<Map<String, String>> listOfRows);

void createDatastore(String datastoreName, Map<String, DataStoreColumnType> columnsMap);

void insertRowsAsMap(String datastoreName, List<Map<String, String>> listOfRows);

void insertRowsAsList(String datastoreName, List<List<String>> listOfRows);

Long insertRow(String datastoreName, DataStoreRow row);

Long insertRow(String datastoreName, DataStoreRow row, boolean create);

Long insertRow(String datastoreName, Map<String, String> row);

Long insertRow(String datastoreName, Map<String, String> row, boolean create);

Long insertRow(String dataStoreName, String json, boolean create);

Long insertRow(String dataStoreName, String json);

Integer updateQuery(String datastoreName, String query);

void deleteQuery(String datastoreName, String query);

String beginTransaction();

void commitTransaction();

void closeTransaction();

String getTransaction();

Example of work with ODF Transactions

Example of ODF Transaction usage
public class DatastoreTransactionService implements TransactionService {

private static final String TRANSACTION_DS_NAME = "my_datastore";

private static final String READ_TRANSACTION_QUERY_TEMPLATE = "select * from @this where transaction_id = '%s'";
private static final String UPDATE_TRANSACTION_QUERY_TEMPLATE = "update @this set transaction_data = '%s' where transaction_id = '%s'";
private static final String DELETE_TRANSACTION_QUERY_TEMPLATE = "delete from @this where transaction_id = '%s'";

private final DatastorePluginAdapter datastorePluginAdapter;

private final Logger logger;
private final Binding binding;

@Inject
public DatastoreTransactionService(DatastorePluginAdapter datastorePluginAdapter, Logger logger, Binding binding) {
this.datastorePluginAdapter = datastorePluginAdapter;
this.logger = logger;
this.binding = binding;
}

@Override
public void save(Transaction transaction) {
String id = transaction.getId();
String runId = BindingUtils.getBpRunId(binding);
String data = InternalStorageFormats.toJson(transaction);

DataStoreRow dataStoreRow = new DataStoreRow();
dataStoreRow.addValue("transaction_id", DataStoreColumnType.TEXT, id);
dataStoreRow.addValue("bp_run_id", DataStoreColumnType.TEXT, runId);
dataStoreRow.addValue("transaction_data", DataStoreColumnType.TEXT, data);

datastorePluginAdapter.insertRow(TRANSACTION_DS_NAME, dataStoreRow);
}

@Override
public Transaction byId(String transactionId) {
List<DbRowVariable> result = datastorePluginAdapter.selectQuery(TRANSACTION_DS_NAME, String.format(READ_TRANSACTION_QUERY_TEMPLATE, transactionId));
if (result.isEmpty()) {
logger.error("ODF transaction with id={} does not exist", transactionId);
return null;
}

return result.stream()
.map(row -> row.get("transaction_data").toString())
.map(InternalStorageFormats::fromJson)
.findFirst()
.get();
}

@Override
public void update(Transaction transaction) {
try {
String id = transaction.getId();
String updateTransactionQuery = String.format(UPDATE_TRANSACTION_QUERY_TEMPLATE,
StringEscapeUtils.escapeSql(InternalStorageFormats.toJson(transaction)), StringEscapeUtils.escapeSql(id));
datastorePluginAdapter.updateQuery(TRANSACTION_DS_NAME, updateTransactionQuery);
} catch (Exception e) {
throw new IllegalStateException("Cannot update the transaction", e);
}
}

@Override
public void delete(String transactionId) {
datastorePluginAdapter.deleteQuery(TRANSACTION_DS_NAME, String.format(DELETE_TRANSACTION_QUERY_TEMPLATE, transactionId));
}
}

Example of reading from regular Data Store

Example of SimpleDatastoreDao class usage
public class SimpleDatastoreDao {

private static final String DATASTORE_NAME = "my_datastore";
private static final String READ_QUERY_TEMPLATE = "select * from @this";

private final DatastorePluginAdapter datastorePluginAdapter;

@Inject
public SimpleDatastoreDao(DatastorePluginAdapter datastorePluginAdapter) {
this.datastorePluginAdapter = datastorePluginAdapter;
}

public List<DbRowVariable> selectAll() {
return datastorePluginAdapter.selectQuery(DATASTORE_NAME, READ_QUERY_TEMPLATE);
}

}
Example of TransactionSupplierExample class usage
public class TransactionSupplierExample implements TransactionSupplier {

private final SimpleDatastoreDao datastoreDao;

@Inject
public TransactionSupplierExample(SimpleDatastoreDao datastoreDao) {
this.datastoreDao = datastoreDao;
}

@Override
public Collection<Transaction> get() {
return datastoreDao.selectAll().stream()
.map(this::createDocument)
.map(this::createTransaction)
.collect(Collectors.toList());
}

private Document createDocument(DbRowVariable row) {
Document document = new Document();
document.setId(uuid());
for (int i = 0; i < row.getColumnCount(); i++) {
String name = row.getColumnName(i);
String value = row.get(i).toString();
document.putAttribute(name, value);
}
return document;
}

private Transaction createTransaction(Document document) {
Transaction transaction = new Transaction();
transaction.setId(uuid());
transaction.setDocs(Collections.singletonList(document));
return transaction;
}

private static String uuid() {
return UUID.randomUUID().toString();
}

}
Example of Bot Task invoking TransactionSupplierExample class
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://web-harvest.sourceforge.net/schema/1.0/config" scriptlang="groovy">
<script><![CDATA[
import com.ibank.automation.refactorexercise.app.AppExample
import com.ibank.automation.refactorexercise.supplier.TransactionSupplierExample

def app = AppExample.init(binding).get();
def transactions = app.loadTransactions(TransactionSupplierExample.class);

result = [];
transactions.each {
result << ['transaction_id': it.id];
}
]]></script>

<export include-original-data="false">
<multi-column list="${result}" split-results="true">
<put-to-column-getter name="_sys_transaction_id" property="transaction_id"/>
</multi-column>
</export>
</config>
note

DatastorePluginAdapter returns the DbRowVariable object instead of List<Map<String, String>> as it was prior to ODF v10.0.

If you are migrating from an older ODF version and you have to work with List<Map<String, String>>, the following example might be helpful.

Expand to see the sample code
public class SimpleDatastoreDao {

private static final String DATASTORE_NAME = "my_datastore";
private static final String READ_QUERY_TEMPLATE = "select * from @this";

private final DatastorePluginAdapter datastorePluginAdapter;

@Inject
public SimpleDatastoreDao(DatastorePluginAdapter datastorePluginAdapter) {
this.datastorePluginAdapter = datastorePluginAdapter;
}

public List<DbRowVariable> selectAll() {
return datastorePluginAdapter.selectQuery(DATASTORE_NAME, READ_QUERY_TEMPLATE);
}

public List<Map<String, String>> selectAllAsListOfMaps() {
return transformToListOfMaps(selectAll());
}

private static List<Map<String, String>> transformToListOfMaps(List<DbRowVariable> rows) {
List<Map<String, String>> result = new ArrayList<>();
for (DbRowVariable row : rows) {
Map<String, String> map = new LinkedHashMap<>();
result.add(map);
for (int i = 0; i < row.getColumnCount(); i++) {
String name = row.getColumnName(i);
String value = row.get(i).toString();
map.put(name, value);
}
}
return result;
}

}

Transaction Service

The com.workfusion.rpa.core.plugin.datastore.DatastorePluginAdapter interface is used for all Data Store operations including transactions.

/**
* Nested transactions are not supported, because they are not supported by Workfusion.
* As you can see
* <a href='https://doc.workfusion.com/platform/docs/automate/control-tower/datastore-plugins/#create-datastore'>here</a>
* what Workfusion is calling "nested" transaction "is executed like a separate transaction".
* It means that ANY transaction is committed to the datastore itself, which is not acceptable for inner transactions.
*
* @return id of opened transaction
* @throws java.lang.IllegalArgumentException if transaction is already opened
*/
String beginTransaction();

/**
* @throws java.lang.IllegalArgumentException if transaction is already opened
*/
void commitTransaction();

/**
* Close transaction without committing it.
*/
void closeTransaction();

String getTransaction();
Example of Transaction Service usage
public class TransactionServiceExample {

private final DatastorePluginAdapter datastorePluginAdapter;

@Inject
public TransactionServiceExample(DatastorePluginAdapter datastorePluginAdapter) {
this.datastorePluginAdapter = datastorePluginAdapter;
}

public <T> T process(Supplier<T> transactionalOperation) {
try {
datastorePluginAdapter.beginTransaction();
T result = transactionalOperation.get();
datastorePluginAdapter.commitTransaction();
return result;
} catch (Exception e) {
datastorePluginAdapter.closeTransaction();
throw new IllegalStateException("Exception occurred while trying to perform transactional operation", e);
}
}

}

Work with Secrets Vault

The com.workfusion.rpa.core.plugin.security.SecretsVaultPluginAdapter interface provides the following methods:

Boolean putSecretsVault(String alias, String key, String value);

Optional<SecureEntryDTO> getSecretsVault(String alias, String provider);

Optional<SecureEntryDTO> getSecretsVault(String alias);

Boolean deleteFromSecretsVault(String alias);

Boolean updateSecretsVault(String alias, String key, String value);

Boolean resetSecretsVault(String alias);

SecretsVaultPluginAdapter can be obtained from PluginAdapterFactory by the following way:

SecretsVaultPluginAdapter secretsVaultPluginAdapter = pluginAdapterFactory.getPluginAdapter(PluginAdapterFactory.PluginsEnum.SECRETS_VAULT);
Example of Module with SecretsVaultPluginAdapter usage
public class ExampleModuleWithSecretVault implements Module {

@Provides
public SecretsVaultPluginAdapter secretsVaultPluginAdapter(PluginAdapterFactory pluginAdapterFactory) {
return pluginAdapterFactory.getPluginAdapter(PluginAdapterFactory.PluginsEnum.SECRETS_VAULT);
}

}

public class ExampleTransactionWithSecretVault implements TransactionSupplier {

private final SecretsVaultPluginAdapter secretsVaultPluginAdapter;

@Inject
public ExampleTransactionWithSecretVault(SecretsVaultPluginAdapter secretsVaultPluginAdapter) {
this.secretsVaultPluginAdapter = secretsVaultPluginAdapter;
}

void doSomething() {
Optional<SecureEntryDTO> secureEntry = secretsVaultPluginAdapter.getSecretsVault("my_secret");
if (secureEntry.isPresent()) {
String key = secureEntry.get().getKey();
String value = secureEntry.get().getValue();
}
}

}

Work with Emails

ODF provides Java API for sending notification emails:

public interface EmailSender {

/**
* Send e-mail message.
*
* @param message Message to send.
* @throws EmailMessagingException E-mail sending problems.
*/
void sendEmail(EmailMessage message) throws EmailMessagingException;

/**
* Send e-mail message.
*
* @param from Sender e-mail address.
* @param to Recipient e-mail address.
* @param subject Message subject.
* @param body Message body text.
* @throws EmailMessagingException E-mail sending problems.
*/
void sendEmail(String from, String to, String subject, String body) throws EmailMessagingException;

/**
* Send e-mail message.
*
* @param from Sender e-mail address.
* @param to List of recipient e-mail addresses.
* @param subject Message subject.
* @param body Message body text.
* @throws EmailMessagingException E-mail sending problems.
*/
void sendEmail(String from, List<String> to, String subject, String body) throws EmailMessagingException;
}

Configuration is done via SMTPEmailSender that you can instantiate and fill with credentials stored in Secrets Vault and global variables:

    /**
* @param host
* @param port
* @param user
* @param password
*/
public SMTPEmailSender(final SupportedProtocol protocol, final String host, final String port, final String user, final String password) {
this.protocol = protocol;
this.host = host;
this.port = port;
this.user = user;
this.password = password;

initStore();
}
Example of usage with injection
public class EmailNotificationExample {

private final SMTPEmailSender smtpEmailSender;

@Inject
public EmailNotificationExample(SMTPEmailSender smtpEmailSender) {
this.smtpEmailSender = smtpEmailSender;
}

public void sendNotificationEmail() throws EmailMessagingException {
String from = "<from e-mail>";
String to = "<to e-mail>";
String subject = "Test message";
String body = "Hello,\n The batch is executed successfully.";

smtpEmailSender.sendMail(from, to, subject, body);
}
}

Sample send Email solution

try {
SecureEntryDTO secureEntry = securityUtils.getSecureEntry("GmailAccountAlias");
smtpEmailSender = new SMTPEmailSender(supportedProtocol, "smtp.gmail.com", "465", secureEntry.getKey(), secureEntry.getValue());
smtpEmailSender.sendEmail(secureEntry.getKey(), secureEntry.getKey(), topic, message);
transaction.putAttribute("status", "SUCCESS");
} catch (EmailMessagingException e) {
transaction.putAttribute("status", "FAILED");
}

Configure EmailTransactionConnector to use nested S3 folder

To set up EmailTransactionConnector to use the nested folder, the mail_s3_folder parameter (or the EmailTransactionConnector.EmailSettings.MAIL_S3_FOLDER constant) should be specified:

def params = ['mail_s3_folder': 'my/super/folder/'];
def transactions = Intake.init(binding).params(params).get().loadTransactions(EmailTransactionConnector.class);