Skip to content

Commit

Permalink
Data Access Layer - Database (#707)
Browse files Browse the repository at this point in the history
* Initial commit of docker comose and connection

* Autowiring

* Adding comments

* Re-adding inject

* return conn option

* Added db inserts and refactored naming

* wrapper class for DriverManager and conn

* singleton

* etorsqldrivermanager registered in app. context

* Adding to tests

* minor refactoring + getConnection unhappy path unit test

* no synchronization, keep it simple

* closeConnection unit test unhappy path

* refactoring changes, delete try catch

* commented out failing tests

* all tests passed
Mock(Connection) substitured for Connection mockConnection

* deleted logger.info that was used for testing

* major refactoring of the posgresdao, all tests pass

* try catch with resources

* Fixing issue with e2e tests

* Fixing linter

* exclude EtorSqlDriverManager

* Adding env props

* Added upsertMetadata sqlException unit test

- test is passing as expected

* WIP: Adding select functionality to retrieve metadata

* refactoring of unit test

* refactoring upsertMetadat unhappy path unit test

* env vars for database - all mocked

* ternaries for properties object - all tests pass

* @OverRide annotation for fetchMetadata

* refactoring:
ApplicationContext if/else for file or db
Changes to EtorDomainRegistration, adding uniqueId

* Adding mvp select feature and fixing domain registration to allow both local file sender and the database to start

* Adding tests and refactoring pgDao

* Fixing lint issues

* testing terraform

* Fixing sku

* Fixing pass

* removing bad value

* Adding missing values

* tweaking terraform

* further tf tweaks

* adding ignore

* Updating db name for app

* updating auth

* Fixing oops

* Adding AD auth

* Fixing error

* Resolving http issue

* Test getting a token to read/write from the deployed database

* Change the db name

* Add a bunch of logging

* Register the db implementations when not locally

* Added TODO to figure out the right token request URL

* Adding default to url

* Fixing errors

* Trying logger tweak

* Updating ssl options

* Updating terraform

* Fixing formatting of tf file

* Updating deploy for internal

* Fixing needy terraform

* Need space from terraform

* Generifying terraform deploy step

* Broadening permissions for db

* Tweaking terraform

* Adding Security access setting default to true

* Firewall rules for db

* Removing dev sepcific firewalls

* Adding dual connection options

* 672: throw PartnerMetadataException instead of RuntimeException when cannot save metadata

* 672: Cleaned-up exception chain and properly handling no results found with the Optional

* 672: Use timestampz to store the date and times for a metadata event

* 672: Moved Azure credentialing process to the AzureClient so it can be mocked for the DB tests

* Fixing terraform formating

* Fixing azure owner issue

* Fixing Azure table issue

* Removing alter table statment as this breaks the deploy

* Re-adding alter statement

* Reverting commits

* Fixing merge conflicts

* Formatting

* adding saveParnerMetadat() call

* deleted partnerMetadataStorage.saveMetadata() used in testing

* add persistence to postgres container and get postgres:16 image

* Update docker-compose.postgres.yml

* Update docker-compose.postgres.yml

* TODO comment reminders to delete code used for testing

* refactored code to align with main

* registering Azure Client

* application fails when FilePartnerMetadataStorage is registered

* Reverting EtorDomainRegistration changes to make app run

* Fixing failing tests

* Adding java-docs

* Formatting

* Fixing merge issues

* Removing comments and logging and updating domain registration

* Moving azure client registration

* Fix linting

---------

Co-authored-by: Jeff Crichlake <[email protected]>
Co-authored-by: tjohnson7021 <[email protected]>
Co-authored-by: halprin <[email protected]>
Co-authored-by: jcrichlake <[email protected]>
  • Loading branch information
5 people authored Jan 3, 2024
1 parent 4480934 commit aa6877f
Show file tree
Hide file tree
Showing 23 changed files with 497 additions and 8 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/terraform-deploy_reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ jobs:
- name: Terraform Apply
run: terraform apply -auto-approve -input=false ${{ inputs.TERRAFORM_APPLY_PARAMETERS }}

- name: Login via Azure CLI
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Run Db migration
run: |
export PGPASSWORD=$(az account get-access-token --resource-type oss-rdbms --query "[accessToken]" -o tsv)
psql "host=$(terraform output -raw database_hostname) port=5432 dbname=postgres user=cdcti-github sslmode=require" -c "CREATE TABLE IF NOT EXISTS metadata (message_id varchar(30), sender varchar(30), receiver varchar(30), hash_of_order varchar(1000), time_received timestamptz); GRANT ALL ON metadata TO azure_pg_admin; ALTER TABLE metadata OWNER TO azure_pg_admin;"
- id: export-terraform-output
name: Export Terraform Output
run: |
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ ext.jacoco_excludes = [
'**/jjwt/JjwtEngine*',
'**/apache/ApacheClient*',
'**/azure/AzureSecrets*',
'**/database/EtorSqlDriverManager*',
'**/azure/AzureClient*'
]

Expand Down
17 changes: 17 additions & 0 deletions docker-compose.postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3.7"

services:
postgresql:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: "intermediary"
POSTGRES_PASSWORD: "changeIT!"
POSTGRES_USER: "intermediary"
ports:
- 5433:5432
volumes:
- ti_postgres_data:/var/lib/postgresql/data

volumes:
ti_postgres_data:
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@
import gov.hhs.cdc.trustedintermediary.etor.orders.UnableToSendOrderException;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureClient;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureStorageAccountPartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.external.database.DatabasePartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.external.database.EtorSqlDriverManager;
import gov.hhs.cdc.trustedintermediary.external.database.PostgresDao;
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter;
import gov.hhs.cdc.trustedintermediary.external.localfile.FilePartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.external.localfile.LocalFileOrderSender;
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamEndpointClient;
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamOrderSender;
import gov.hhs.cdc.trustedintermediary.wrappers.DbDao;
import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.SqlDriverManager;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -79,15 +84,24 @@ public Map<HttpEndpoint, Function<DomainRequest, DomainResponse>> domainRegistra
ApplicationContext.register(
ReportStreamEndpointClient.class, ReportStreamEndpointClient.getInstance());

if (ApplicationContext.getEnvironment().equalsIgnoreCase("local")) {
ApplicationContext.register(OrderSender.class, LocalFileOrderSender.getInstance());
if (ApplicationContext.getProperty("DB_URL") != null) {
ApplicationContext.register(SqlDriverManager.class, EtorSqlDriverManager.getInstance());
ApplicationContext.register(DbDao.class, PostgresDao.getInstance());
ApplicationContext.register(
PartnerMetadataStorage.class, DatabasePartnerMetadataStorage.getInstance());
} else if (ApplicationContext.getEnvironment().equalsIgnoreCase("local")) {
ApplicationContext.register(
PartnerMetadataStorage.class, FilePartnerMetadataStorage.getInstance());
} else {
ApplicationContext.register(OrderSender.class, ReportStreamOrderSender.getInstance());
ApplicationContext.register(
PartnerMetadataStorage.class,
AzureStorageAccountPartnerMetadataStorage.getInstance());
}

if (ApplicationContext.getEnvironment().equalsIgnoreCase("local")) {
ApplicationContext.register(OrderSender.class, LocalFileOrderSender.getInstance());
} else {
ApplicationContext.register(OrderSender.class, ReportStreamOrderSender.getInstance());
ApplicationContext.register(AzureClient.class, AzureClient.getInstance());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.hhs.cdc.trustedintermediary.external.azure;

import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
Expand Down Expand Up @@ -40,4 +41,11 @@ public static AzureClient getInstance() {
public BlobClient getBlobClient(String blobName) {
return BLOB_CONTAINER_CLIENT.getBlobClient(blobName);
}

public String getScopedToken(String scope) {
return new DefaultAzureCredentialBuilder()
.build()
.getTokenSync(new TokenRequestContext().addScopes(scope))
.getToken();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package gov.hhs.cdc.trustedintermediary.external.database;

import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.wrappers.DbDao;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import java.sql.SQLException;
import java.util.Optional;
import javax.inject.Inject;

/** Implements the {@link PartnerMetadataStorage} using a database. */
public class DatabasePartnerMetadataStorage implements PartnerMetadataStorage {

@Inject DbDao dao;

@Inject Logger logger;
private static final DatabasePartnerMetadataStorage INSTANCE =
new DatabasePartnerMetadataStorage();

Expand All @@ -17,10 +25,28 @@ public static DatabasePartnerMetadataStorage getInstance() {
}

@Override
public Optional<PartnerMetadata> readMetadata(final String uniqueId) {
return Optional.empty();
public Optional<PartnerMetadata> readMetadata(final String uniqueId)
throws PartnerMetadataException {
try {
PartnerMetadata data = (PartnerMetadata) dao.fetchMetadata(uniqueId);
return Optional.ofNullable(data);
} catch (SQLException e) {
throw new PartnerMetadataException("Error retrieving metadata", e);
}
}

@Override
public void saveMetadata(final PartnerMetadata metadata) {}
public void saveMetadata(final PartnerMetadata metadata) throws PartnerMetadataException {
logger.logInfo("saving the metadata");
try {
dao.upsertMetadata(
metadata.receivedSubmissionId(),
metadata.sender(),
metadata.receiver(),
metadata.hash(),
metadata.timeReceived());
} catch (SQLException e) {
throw new PartnerMetadataException("Error saving metadata", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gov.hhs.cdc.trustedintermediary.external.database;

import gov.hhs.cdc.trustedintermediary.wrappers.SqlDriverManager;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/** Wrapper class for SqlDriverManager */
public class EtorSqlDriverManager implements SqlDriverManager {

private static final EtorSqlDriverManager INSTANCE = new EtorSqlDriverManager();

private EtorSqlDriverManager() {}

@Override
public Connection getConnection(String url, Properties props) throws SQLException {
return DriverManager.getConnection(url, props);
}

public static EtorSqlDriverManager getInstance() {
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package gov.hhs.cdc.trustedintermediary.external.database;

import gov.hhs.cdc.trustedintermediary.context.ApplicationContext;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureClient;
import gov.hhs.cdc.trustedintermediary.wrappers.DbDao;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.SqlDriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Properties;
import javax.inject.Inject;

/** Class for accessing and managing data for the postgres Database */
public class PostgresDao implements DbDao {

private static final PostgresDao INSTANCE = new PostgresDao();

@Inject Logger logger;
@Inject SqlDriverManager driverManager;
@Inject AzureClient azureClient;

private PostgresDao() {}

protected Connection connect() throws SQLException {
Connection conn;
String url =
"jdbc:postgresql://"
+ ApplicationContext.getProperty("DB_URL")
+ ":"
+ ApplicationContext.getProperty("DB_PORT")
+ "/"
+ ApplicationContext.getProperty("DB_NAME");

logger.logInfo("going to connect to db url {}", url);

// Ternaries prevent NullPointerException during testing since we decided not to mock env
// vars.
String user =
ApplicationContext.getProperty("DB_USER") == null
? ""
: ApplicationContext.getProperty("DB_USER");
String pass =
ApplicationContext.getProperty("DB_PASS") == null
? ""
: ApplicationContext.getProperty("DB_PASS");
String ssl =
ApplicationContext.getProperty("DB_SSL") == null
? ""
: ApplicationContext.getProperty("DB_SSL");

Properties props = new Properties();
props.setProperty("user", user);
logger.logInfo("About to get the db password");

String token =
pass.isBlank()
? azureClient.getScopedToken(
"https://ossrdbms-aad.database.windows.net/.default")
: pass;

logger.logInfo("got the db password");

props.setProperty("password", token);

// If the below prop isn't set to require and we just set ssl=true it will expect a CA cert
// in azure which breaks it
props.setProperty("sslmode", ssl);
conn = driverManager.getConnection(url, props);
logger.logInfo("DB Connected Successfully");
return conn;
}

public static PostgresDao getInstance() {
return INSTANCE;
}

@Override
public synchronized void upsertMetadata(
String receivedSubmissionId,
String sender,
String receiver,
String hash,
Instant timeReceived)
throws SQLException {

try (Connection conn = connect();
PreparedStatement statement =
conn.prepareStatement("INSERT INTO metadata VALUES (?, ?, ?, ?, ?)")) {
// TODO: Update the below statement to handle on conflict, after we figure out what that
// behavior should be
statement.setString(1, receivedSubmissionId);
statement.setString(2, sender);
statement.setString(3, receiver);
statement.setString(4, hash);
statement.setTimestamp(5, Timestamp.from(timeReceived));

statement.executeUpdate();
}
}

@Override
public synchronized PartnerMetadata fetchMetadata(String receivedSubmissionId)
throws SQLException {
try (Connection conn = connect();
PreparedStatement statement =
conn.prepareStatement("SELECT * FROM metadata where message_id = ?")) {

statement.setString(1, receivedSubmissionId);

ResultSet result = statement.executeQuery();

var hasValidData = result.next();
if (!hasValidData) {
return null;
}

return new PartnerMetadata(
result.getString("message_id"),
result.getString("receiver"),
result.getTimestamp("time_received").toInstant(),
result.getString("hash_of_order"));
}
}
}
Loading

0 comments on commit aa6877f

Please sign in to comment.