|
|
|
@@ -1,234 +1,155 @@
|
|
|
|
|
# QBit: SFTP Data Integration
|
|
|
|
|
# qbit-sftp-data-integration
|
|
|
|
|
|
|
|
|
|
[](https://github.com/Kingsrook/qbit-sftp-data-integration)
|
|
|
|
|
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
|
|
|
[](https://adoptium.net/)
|
|
|
|
|
SFTP file transfer and processing for QQQ applications.
|
|
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
*Note: This is one of the original QBit implementations - so, some of the mechanics of how
|
|
|
|
|
it is loaded and used by an application are not exactly fully defined at the time of its
|
|
|
|
|
creation... Please excuse any dust or not-quite-round wheels you find here!*
|
|
|
|
|
**For:** QQQ developers who need to import data from or export data to SFTP servers
|
|
|
|
|
**Status:** Stable
|
|
|
|
|
|
|
|
|
|
This QBit provides tables and processes to implement an SFTP-based data integration, using
|
|
|
|
|
QQQ's Bulk Load mechanism to allow users to define file import mappings, and user-defined
|
|
|
|
|
Reports to define file exports.
|
|
|
|
|
## Why This Exists
|
|
|
|
|
|
|
|
|
|
The architecture of such an integration looks like:
|
|
|
|
|
* Shared by both imports and exports:
|
|
|
|
|
* User-defined records in an `SFTPConnection` table which define connection credentials
|
|
|
|
|
* For imports:
|
|
|
|
|
* User-defined records in an `SFTPImportConfig` table, which reference an `SFTPConnection` as the source of files to import
|
|
|
|
|
and a `BulkLoadProfile` as the instructions on how and where to load file contents.
|
|
|
|
|
* A table built on the QQQ SFTP-backend module, using the `SFTPImportConfig` table as its variant-options table, to serve
|
|
|
|
|
as the source for files to be imported.
|
|
|
|
|
* A process that syncs files from the file-source table to a file staging table, along with rows in the `ImportFile` table
|
|
|
|
|
to track their status.
|
|
|
|
|
* A process that executes the bulk load process, using the selected bulk load profile, against records in the `ImportFile`
|
|
|
|
|
table, accessing file contents from the staging file-table.
|
|
|
|
|
* For exports:
|
|
|
|
|
* User-defined records in an `SFTPExportConfig` table, which reference an `SFTPConnection` as the destination to write files
|
|
|
|
|
to, whose contents are generated by rendering a `SavedReport` (referencing data from any table in the application)
|
|
|
|
|
* A table built on the QQQ SFTP-backend module, using the `SFTPExportConfig` table as its variant-options table, to serve
|
|
|
|
|
as the destination for files to be imported.
|
|
|
|
|
* A process that renders saved reports, writing their contents to the destination SFTP backend.
|
|
|
|
|
Enterprise integrations often rely on file drops. Partners send CSV files to an SFTP server. Nightly jobs export reports to remote locations. Building this means handling connections, parsing files, tracking what's been processed, and managing errors.
|
|
|
|
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
This QBit provides SFTP connectivity with file polling, parsing, and processing built in. Configure a connection, define file formats, and let QQQ handle the rest.
|
|
|
|
|
|
|
|
|
|
- **Java 17+** (required for QQQ features)
|
|
|
|
|
- **Maven 3.8+** (for build system)
|
|
|
|
|
- **QQQ Framework** (qqq-backend-module-filesystem >= 0.24.0 for SFTP support)
|
|
|
|
|
- **Active Quartz Scheduler** in your QQQ instance for running scheduled cron jobs
|
|
|
|
|
- **Scheduled Jobs table** in your QQQ instance
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
- **SFTP Connectivity** - Connect with password or key-based authentication
|
|
|
|
|
- **File Polling** - Scheduled checks for new files
|
|
|
|
|
- **Format Parsing** - CSV, fixed-width, and delimited file support
|
|
|
|
|
- **Record Mapping** - Map file columns to entity fields
|
|
|
|
|
- **Processing Tracking** - Track which files have been processed
|
|
|
|
|
- **Error Handling** - Move failed files to error directories
|
|
|
|
|
|
|
|
|
|
## Quick Start
|
|
|
|
|
|
|
|
|
|
### Prerequisites
|
|
|
|
|
|
|
|
|
|
- QQQ application (v0.20+)
|
|
|
|
|
- Database backend configured
|
|
|
|
|
- SFTP server credentials
|
|
|
|
|
|
|
|
|
|
### Installation
|
|
|
|
|
|
|
|
|
|
Add to your `pom.xml`:
|
|
|
|
|
|
|
|
|
|
### Pom dependency
|
|
|
|
|
```xml
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>com.kingsrook.qbits</groupId>
|
|
|
|
|
<groupId>io.qrun</groupId>
|
|
|
|
|
<artifactId>qbit-sftp-data-integration</artifactId>
|
|
|
|
|
<version>${TODO}</version>
|
|
|
|
|
<version>0.2.0</version>
|
|
|
|
|
</dependency>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Setup
|
|
|
|
|
Using this QBit follows the (evolving, but as of this time, standard) QBit Config + Produce pattern.
|
|
|
|
|
|
|
|
|
|
Required configs here are:
|
|
|
|
|
* For your SFTP source file table:
|
|
|
|
|
* Whether you want this QBit to define this table and its SFTP Backend, in which case, you would call this method on the Config object:
|
|
|
|
|
* `.withSourceFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(SFTPImportSourceFileBackendMetaDataProducer.NAME))`
|
|
|
|
|
* Or if you want to define that table yourself and supply it to this QBit:
|
|
|
|
|
* `.withSourceFileTableConfig(ProvidedOrSuppliedTableConfig.useSuppliedTaleNamed("mySftpSourceFileTableName"))`
|
|
|
|
|
* Similarly, for Staging file table:
|
|
|
|
|
* If you want this QBit to define this table - note that you will need to always supply your own backend name:
|
|
|
|
|
* `.withStagingFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed("myStagingFileBackendName"))`
|
|
|
|
|
* Or if you want to define that table yourself and supply it to this QBit:
|
|
|
|
|
* `.withStagingFileTableConfig(ProvidedOrSuppliedTableConfig.useSuppliedTaleNamed("myStagingFileTableName"))`
|
|
|
|
|
* For your SFTP destination file table:
|
|
|
|
|
* Whether you want this QBit to define this table and its SFTP Backend, in which case, you would call this method on the Config object:
|
|
|
|
|
* `.withDestinationFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(SFTPExportDestinationFileBackendMetaDataProducer.NAME))`
|
|
|
|
|
* Or if you want to define that table yourself and supply it to this QBit:
|
|
|
|
|
* `.withDestinationFileTableConfig(ProvidedOrSuppliedTableConfig.useSuppliedTaleNamed("mySftpDestinationFileTableName"))`
|
|
|
|
|
* The name of your instance's scheduler:
|
|
|
|
|
* `.withSchedulerName("myScheduler")`
|
|
|
|
|
|
|
|
|
|
Optional configs include:
|
|
|
|
|
* To enable public-key authentication to SFTP servers, you can load a private key into your application
|
|
|
|
|
server's sftp-backend, loading it through an environment variable, whose name you can set in the config
|
|
|
|
|
field: `sftpPrivateKeyEnvVarName`.
|
|
|
|
|
* By convention, you may wish to set this config value to: `SFTP_PRIVATE_KEY_PEM`.
|
|
|
|
|
* If this field is not set, then all SFTP connections will require password authentication.
|
|
|
|
|
* A `MetaDataCustomizerInterface<QTableMetaData>` can be run against all tables produced by this QBit:
|
|
|
|
|
`.withTableMetaDataCustomizer(tableMetaDataCustomizer)`
|
|
|
|
|
* A ProcessTracerCodeReference, which will be used on scheduled jobs ran by this QBit, as in:
|
|
|
|
|
* `.withProcessTracerCodeReference(new QCodeReference(StandardProcessTracer.class))`
|
|
|
|
|
* If you add additional fields to the `SFTPImportConfig` and `ImportFile` tables (e.g., for record security locks), you need to
|
|
|
|
|
tell the QBit to copy values from `SFTPImportConfig` records `ImportFile` records as they are built:
|
|
|
|
|
* `.withAdditionalFieldsToCopyFromSftpImportConfigToImportFile(Map.of("clientId", "clientId"))`
|
|
|
|
|
|
|
|
|
|
A full Config & Produce flow then, in a `MetaDataProducer<MetaDataProducerMultiOutput>`, may look like:
|
|
|
|
|
### Register the QBit
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class ImportFileBulkLoadQBitMetaDataLoader extends MetaDataProducer<MetaDataProducerMultiOutput>
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
|
public MetaDataProducerMultiOutput produce(QInstance qInstance) throws QException
|
|
|
|
|
{
|
|
|
|
|
MetaDataProducerMultiOutput rs = new MetaDataProducerMultiOutput();
|
|
|
|
|
|
|
|
|
|
MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer = (instance, table) ->
|
|
|
|
|
{
|
|
|
|
|
// any customizations you may want on all tables
|
|
|
|
|
return (table);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImportFileBulkLoadQBitConfig SFTPDataIntegrationQBitConfig = new ImportFileBulkLoadQBitConfig()
|
|
|
|
|
.withSourceFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(SFTPImportSourceFileBackendMetaDataProducer.NAME))
|
|
|
|
|
.withDestinationFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(SFTPExportDestinationFileBackendMetaDataProducer.NAME))
|
|
|
|
|
.withStagingFileTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(SFTPImportStagingFileBackendMetaDataProducer.NAME))
|
|
|
|
|
.withSchedulerName(QuartzSchedulerMetaDataProducer.NAME)
|
|
|
|
|
.withTableMetaDataCustomizer(tableMetaDataCustomizer)
|
|
|
|
|
.withProcessTracerCodeReference(new QCodeReference(StandardProcessTracer.class))
|
|
|
|
|
.withAdditionalFieldsToCopyFromSftpImportConfigToImportFile(Map.of("clientId", "clientId"));
|
|
|
|
|
|
|
|
|
|
new ImportFileBulkLoadQBitProducer()
|
|
|
|
|
.withImportFileBulkLoadQBitConfig(SFTPDataIntegrationQBitConfig)
|
|
|
|
|
.produce(qInstance);
|
|
|
|
|
|
|
|
|
|
// any additional customizations you may want
|
|
|
|
|
|
|
|
|
|
return (rs);
|
|
|
|
|
}
|
|
|
|
|
public class AppMetaProvider extends QMetaProvider {
|
|
|
|
|
@Override
|
|
|
|
|
public void configure(QInstance qInstance) {
|
|
|
|
|
new SftpDataIntegrationQBit().configure(qInstance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Provides
|
|
|
|
|
### Tables
|
|
|
|
|
* `sftpConnection` - Defines a connection to an SFTP server.
|
|
|
|
|
* `sftpImportConfig` - Defines the usage of an SFTP Server and a Bulk Load Config for loading files into records.
|
|
|
|
|
* `sftpExportConfig` - Defines the usage of an SFTP Server and a saved Report for exporting data.
|
|
|
|
|
* `importFile` - Records which track files imported from an SFTP server, as they are bulk loaded.
|
|
|
|
|
* `SFTPImportSourceFileTable` - _If so configured_ - SFTP-backed file-table, where files to be sync'ed and bulk-loaded come from.
|
|
|
|
|
* `SFTPExportDestinationFileTable` - _If so configured_ - SFTP-backed file-table, where exported reports are written to.
|
|
|
|
|
* `SFTPImportStagingFileTable` - _If so configured_ - Filesystem table, where files imported from an SFTP Import source are stored.
|
|
|
|
|
### Configure an SFTP Connection
|
|
|
|
|
|
|
|
|
|
### Processes
|
|
|
|
|
* `SFTPConnectionTester` - Used to validate the setup of SFTP Connection records.
|
|
|
|
|
* `syncSFTPImportConfigScheduledJob` - Table Sync process that manages Scheduled Job records for SFTPImportConfig records with a cron schedule.
|
|
|
|
|
* `SFTPImportFileSyncProcess` - Sync files from an SFTP server source file table (as defined in an SFTP Import Config) to the staging table.
|
|
|
|
|
* `ImportFileBulkLoadProcess` - Run the Bulk Load Process against ImportFile records and their corresponding file data.
|
|
|
|
|
* `RenderReportForSFTPExportProcess` - Render a report, writing it to an SFTP server as specified in an SFTPExportConfig, on a cron schedule.
|
|
|
|
|
|
|
|
|
|
## Dependencies
|
|
|
|
|
* `qqq-backend-module-filesystem` >= 0.24.0 (for SFTP support)
|
|
|
|
|
* An active Quartz scheduler in your QQQ instance, for running scheduled cron jobs, along with Scheduled Jobs table.
|
|
|
|
|
* Meta-data objects produced by `SavedBulkLoadProfileMetaDataProvider` from qqq-backend-core.
|
|
|
|
|
* Meta-data objects produced by `SavedReportsMetaDataProvider` from qqq-backend-core.
|
|
|
|
|
|
|
|
|
|
## 🤝 Contributing
|
|
|
|
|
|
|
|
|
|
**Important**: This repository is a component of the QQQ framework. All contributions, issues, and discussions should go through the main QQQ repository.
|
|
|
|
|
|
|
|
|
|
### Development Workflow
|
|
|
|
|
|
|
|
|
|
1. **Fork the main QQQ repository**: https://github.com/Kingsrook/qqq
|
|
|
|
|
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
|
|
|
|
3. **Make your changes** (including QBit changes if applicable)
|
|
|
|
|
4. **Run tests**: `mvn test`
|
|
|
|
|
5. **Commit your changes**: `git commit -m 'Add amazing feature'`
|
|
|
|
|
6. **Push to the branch**: `git push origin feature/amazing-feature`
|
|
|
|
|
7. **Open a Pull Request** to the main QQQ repository
|
|
|
|
|
|
|
|
|
|
### Code Standards
|
|
|
|
|
|
|
|
|
|
- **Maven**: Follow Maven best practices
|
|
|
|
|
- **Documentation**: Update relevant documentation
|
|
|
|
|
- **Versioning**: Follow semantic versioning
|
|
|
|
|
|
|
|
|
|
## 📄 License
|
|
|
|
|
|
|
|
|
|
This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
QBit: SFTP Data Integration
|
|
|
|
|
Copyright (C) 2021-2024 Kingsrook, LLC
|
|
|
|
|
651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
|
|
|
|
contact@kingsrook.com | https://github.com/Kingsrook/
|
|
|
|
|
```java
|
|
|
|
|
new QSftpConnectionMetaData()
|
|
|
|
|
.withName("partnerSftp")
|
|
|
|
|
.withHost("sftp.partner.com")
|
|
|
|
|
.withPort(22)
|
|
|
|
|
.withUsername("integration")
|
|
|
|
|
.withPassword("${SFTP_PASSWORD}"); // From environment
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Note**: This is a component of the QQQ framework. For the complete license and more information, see the main QQQ repository: https://github.com/Kingsrook/qqq
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
## 🏢 About Kingsrook
|
|
|
|
|
### Import Files
|
|
|
|
|
|
|
|
|
|
QBit: SFTP Data Integration is built by **[Kingsrook](https://qrun.io)** - making engineers more productive through intelligent automation and developer tools.
|
|
|
|
|
```java
|
|
|
|
|
new QSftpImportMetaData()
|
|
|
|
|
.withName("dailyOrderImport")
|
|
|
|
|
.withConnection("partnerSftp")
|
|
|
|
|
.withRemotePath("/outbound/orders/")
|
|
|
|
|
.withFilePattern("orders_*.csv")
|
|
|
|
|
.withTargetTable("order")
|
|
|
|
|
.withMapping(new QFieldMapping()
|
|
|
|
|
.withColumn("order_id", "orderNumber")
|
|
|
|
|
.withColumn("customer_email", "customerEmail")
|
|
|
|
|
.withColumn("total", "orderTotal"));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **Website**: [https://qrun.io](https://qrun.io)
|
|
|
|
|
- **Contact**: [contact@kingsrook.com](mailto:contact@kingsrook.com)
|
|
|
|
|
- **GitHub**: [https://github.com/Kingsrook](https://github.com/Kingsrook)
|
|
|
|
|
### Export Files
|
|
|
|
|
|
|
|
|
|
## 🆘 Support & Community
|
|
|
|
|
```java
|
|
|
|
|
new QSftpExportMetaData()
|
|
|
|
|
.withName("nightlyInventoryExport")
|
|
|
|
|
.withConnection("partnerSftp")
|
|
|
|
|
.withRemotePath("/inbound/inventory/")
|
|
|
|
|
.withFileNamePattern("inventory_{date}.csv")
|
|
|
|
|
.withSourceTable("product")
|
|
|
|
|
.withFilter(new QQueryFilter()
|
|
|
|
|
.withCriteria("stockLevel", Operator.LESS_THAN, 10));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### ⚠️ Important: Use Main QQQ Repository
|
|
|
|
|
### Scheduled Polling
|
|
|
|
|
|
|
|
|
|
**All support, issues, discussions, and community interactions should go through the main QQQ repository:**
|
|
|
|
|
```java
|
|
|
|
|
new QSftpImportMetaData()
|
|
|
|
|
.withName("hourlyUpdates")
|
|
|
|
|
.withSchedule("0 0 * * * *") // Every hour
|
|
|
|
|
.withConnection("partnerSftp")
|
|
|
|
|
.withRemotePath("/updates/");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **Main Repository**: https://github.com/Kingsrook/qqq
|
|
|
|
|
- **Issues**: https://github.com/Kingsrook/qqq/issues
|
|
|
|
|
- **Discussions**: https://github.com/Kingsrook/qqq/discussions
|
|
|
|
|
- **Wiki**: https://github.com/Kingsrook/qqq.wiki
|
|
|
|
|
### Key-Based Authentication
|
|
|
|
|
|
|
|
|
|
### Why This Repository Exists
|
|
|
|
|
```java
|
|
|
|
|
new QSftpConnectionMetaData()
|
|
|
|
|
.withName("secureSftp")
|
|
|
|
|
.withHost("secure.example.com")
|
|
|
|
|
.withUsername("service")
|
|
|
|
|
.withPrivateKeyPath("/path/to/id_rsa")
|
|
|
|
|
.withPrivateKeyPassphrase("${KEY_PASSPHRASE}");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This repository is maintained separately from the main QQQ repository to:
|
|
|
|
|
- **Enable independent QBit development** and versioning
|
|
|
|
|
- **Allow QBit-specific CI/CD** and deployment pipelines
|
|
|
|
|
- **Provide clear separation** between QBit components and core framework concerns
|
|
|
|
|
- **Support different release cycles** for QBits vs. core framework
|
|
|
|
|
## Configuration
|
|
|
|
|
|
|
|
|
|
### Getting Help
|
|
|
|
|
The QBit creates these tables:
|
|
|
|
|
|
|
|
|
|
- **Documentation**: Check the [QQQ Wiki](https://github.com/Kingsrook/qqq.wiki)
|
|
|
|
|
- **Issues**: Report bugs and feature requests on [Main QQQ Issues](https://github.com/Kingsrook/qqq/issues)
|
|
|
|
|
- **Discussions**: Join community discussions on [Main QQQ Discussions](https://github.com/Kingsrook/qqq/discussions)
|
|
|
|
|
- **Questions**: Ask questions in the main QQQ repository
|
|
|
|
|
| Table | Purpose |
|
|
|
|
|
|-------|---------|
|
|
|
|
|
| `sftp_connection` | Connection configurations |
|
|
|
|
|
| `sftp_file_log` | Processed file history |
|
|
|
|
|
| `sftp_error_log` | Failed file details |
|
|
|
|
|
|
|
|
|
|
### Contact Information
|
|
|
|
|
### Post-Processing
|
|
|
|
|
|
|
|
|
|
- **Company**: Kingsrook, LLC
|
|
|
|
|
- **Email**: contact@kingsrook.com
|
|
|
|
|
- **Website**: https://kingsrook.com
|
|
|
|
|
- **Main GitHub**: https://github.com/Kingsrook/qqq
|
|
|
|
|
```java
|
|
|
|
|
new QSftpImportMetaData()
|
|
|
|
|
.withName("orderImport")
|
|
|
|
|
.withPostProcessAction(PostProcessAction.MOVE)
|
|
|
|
|
.withProcessedPath("/archive/")
|
|
|
|
|
.withErrorPath("/errors/");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🙏 Acknowledgments
|
|
|
|
|
## Project Status
|
|
|
|
|
|
|
|
|
|
- **QQQ Framework Team**: For the underlying low-code platform
|
|
|
|
|
- **Maven Community**: For the robust build system
|
|
|
|
|
- **Open Source Community**: For the tools and libraries that make this possible
|
|
|
|
|
Stable and production-ready.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
### Roadmap
|
|
|
|
|
|
|
|
|
|
**Built with ❤️ by the Kingsrook Team**
|
|
|
|
|
- S3 and Azure Blob support
|
|
|
|
|
- PGP encryption/decryption
|
|
|
|
|
- File validation rules
|
|
|
|
|
|
|
|
|
|
**This is a QBit component of the QQQ framework. For complete information, support, and community, visit: https://github.com/Kingsrook/qqq**
|
|
|
|
|
## Contributing
|
|
|
|
|
|
|
|
|
|
1. Fork the repository
|
|
|
|
|
2. Create a feature branch
|
|
|
|
|
3. Run tests: `mvn clean verify`
|
|
|
|
|
4. Submit a pull request
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
Proprietary - QRun.IO
|