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>
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);