Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
"java.test.config": {
"name": "Allow Reflection",
"vmArgs": ["--add-opens", "java.base/java.time=ALL-UNNAMED"] // allows the ValidateSdx tests, which use reflection, to run
}
},
"coverage-gutters.coverageFileNames": [
"jacoco.xml"
],
"coverage-gutters.coverageBaseDir": "**/target/site/jacoco"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,35 @@
import com.trihydro.certexpiration.controller.LoopController;
import com.trihydro.library.factory.KafkaFactory;
import com.trihydro.library.helpers.EmailHelper;
import com.trihydro.library.helpers.Utility;
import com.trihydro.library.model.TopicDataWrapper;

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class CertExpirationConsumer {
private CertExpirationConfiguration configProperties;
private Utility utility;
private CertExpirationConfiguration configProperties;
private EmailHelper emailHelper;
private LoopController loopController;
private KafkaFactory kafkaFactory;

@Autowired
public CertExpirationConsumer(CertExpirationConfiguration configProperties, Utility _utility,
EmailHelper _emailHelper, LoopController _loopController, KafkaFactory _kafkaFactory)
public CertExpirationConsumer(CertExpirationConfiguration configProperties,
EmailHelper _emailHelper, LoopController _loopController, KafkaFactory _kafkaFactory)
throws IOException, Exception {
this.configProperties = configProperties;
utility = _utility;
emailHelper = _emailHelper;
loopController = _loopController;
kafkaFactory = _kafkaFactory;
}

public void startKafkaConsumer() throws Exception {
utility.logWithDate("starting..............");
var stringConsumer = kafkaFactory.createStringConsumer(configProperties.getKafkaHostServer() + ":9092",
log.info("starting..............");
var stringConsumer = kafkaFactory.createStringConsumer(configProperties.getKafkaHostServer() + ":9092",
configProperties.getDepositGroup(), configProperties.getDepositTopic());
var stringProducer = kafkaFactory.createStringProducer(configProperties.getKafkaHostServer() + ":9092");

Expand All @@ -51,9 +50,9 @@ public void startKafkaConsumer() throws Exception {
for (var record : records) {
String logTxt = String.format("Found topic %s, submitting to %s for later consumption",
record.topic(), producerTopic);
utility.logWithDate(logTxt);
log.info(logTxt);

TopicDataWrapper tdw = new TopicDataWrapper();
TopicDataWrapper tdw = new TopicDataWrapper();
tdw.setTopic(record.topic());
tdw.setData(record.value());
ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(producerTopic,
Expand All @@ -62,8 +61,8 @@ public void startKafkaConsumer() throws Exception {
}
}
} catch (Exception ex) {
utility.logWithDate(ex.getMessage());
emailHelper.ContainerRestarted(configProperties.getAlertAddresses(), configProperties.getMailPort(),
log.info(ex.getMessage());
emailHelper.ContainerRestarted(configProperties.getAlertAddresses(), configProperties.getMailPort(),
configProperties.getMailHost(), configProperties.getFromEmail(), "Logger Kafka Consumer");
// Re-throw exception to cause container to exit and restart
throw ex;
Expand All @@ -72,7 +71,7 @@ public void startKafkaConsumer() throws Exception {
stringConsumer.close();
stringProducer.close();
} catch (Exception consumerEx) {
consumerEx.printStackTrace();
log.error("Exception", consumerEx);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.trihydro.library.helpers.EmailHelper;
import com.trihydro.library.helpers.Utility;

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.MockConsumer;
import org.apache.kafka.clients.consumer.OffsetResetStrategy;
Expand All @@ -37,6 +38,7 @@
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
@Slf4j
public class CertExpirationConsumerTest {
private static final String TOPIC = "topic";
private static final String PRODUCERTOPIC = "producerTopic";
Expand All @@ -52,8 +54,6 @@ public class CertExpirationConsumerTest {
@Mock
private CertExpirationConfiguration mockConfigProperties;
@Mock
private Utility mockUtility;
@Mock
private EmailHelper mockEmailHelper;
@Mock
private LoopController mockLoopController;
Expand Down Expand Up @@ -115,11 +115,6 @@ public void startKafkaConsumer_SUCCESS() throws Exception {

// Assert
Assertions.assertEquals(1, mockProducer.history().size());

verify(mockUtility).logWithDate("starting..............");
verify(mockUtility).logWithDate("Found topic topic, submitting to producerTopic for later consumption");

verifyNoMoreInteractions(mockUtility);
Assertions.assertTrue(mockConsumer.closed());
Assertions.assertTrue(mockProducer.closed());
}
Expand All @@ -130,16 +125,11 @@ public void startKafkaConsumer_EXCEPTION() throws Exception {
configureConsumerException("Network error");

// Act
Exception ex = assertThrows(KafkaException.class, () -> uut.startKafkaConsumer());
assertThrows(KafkaException.class, () -> uut.startKafkaConsumer());

// Assert
Assertions.assertEquals("Network error", ex.getMessage());
verify(mockUtility).logWithDate("starting..............");

verify(mockUtility).logWithDate("Network error");
verify(mockEmailHelper).ContainerRestarted(any(), any(), any(), any(), any());

verifyNoMoreInteractions(mockUtility);
Assertions.assertTrue(mockConsumer.closed());
Assertions.assertTrue(mockProducer.closed());
}
Expand All @@ -156,12 +146,8 @@ public void startKafkaConsumer_DOUBLEEXCEPTION() throws Exception {
// Assert
Assertions.assertEquals("Mail Exception", ex.getMessage());

verify(mockUtility).logWithDate("starting..............");

verify(mockUtility).logWithDate("Network error");
verify(mockEmailHelper).ContainerRestarted(any(), any(), any(), any(), any());

verifyNoMoreInteractions(mockUtility);
Assertions.assertTrue(mockConsumer.closed());
Assertions.assertTrue(mockProducer.closed());
}
Expand Down
3 changes: 2 additions & 1 deletion cv-data-controller/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.jar
*.jar
*.crt
19 changes: 18 additions & 1 deletion cv-data-controller/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,24 @@
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.trihydro.cvdatacontroller.controller;

import com.trihydro.library.helpers.DateTimeHelper;
import com.trihydro.library.helpers.DateTimeHelperImpl;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
Expand Down Expand Up @@ -31,6 +33,7 @@
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
Expand All @@ -50,20 +53,23 @@
@RequestMapping("active-tim")
@ApiIgnore
@Slf4j
@Import(DateTimeHelperImpl.class)
public class ActiveTimController extends BaseController {

private TimDbTables timDbTables;
private SQLNullHandler sqlNullHandler;
protected Calendar UTCCalendar;
private DateTimeHelper dateTimeHelper;

public ActiveTimController() {
UTCCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
}

@Autowired
public void InjectDependencies(TimDbTables _timDbTables, SQLNullHandler _sqlNullHandler) {
public void InjectDependencies(TimDbTables _timDbTables, SQLNullHandler _sqlNullHandler, DateTimeHelper dateTimeHelper) {
timDbTables = _timDbTables;
sqlNullHandler = _sqlNullHandler;
this.dateTimeHelper = dateTimeHelper;
}

/**
Expand Down Expand Up @@ -305,21 +311,27 @@ public ResponseEntity<List<Integer>> GetActiveTimIndicesByRsu(@PathVariable Stri
}

@RequestMapping(value = {"/client-id-direction/{clientId}/{timTypeId}", "/client-id-direction/{clientId}/{timTypeId}/{direction}"}, method = RequestMethod.GET)
public ResponseEntity<List<ActiveTim>> GetActiveTimsByClientIdDirection(@PathVariable String clientId, @PathVariable Long timTypeId, @PathVariable(required = false) String direction) {
public ResponseEntity<List<ActiveTim>> getActiveTimsByClientIdDirection(@PathVariable String clientId, @PathVariable Long timTypeId, @PathVariable(required = false) String direction) {
List<ActiveTim> activeTims = new ArrayList<>();

// There may be multiple TIMs grouped together by client_id. ex. CLIENTID_1,
// CLIENTID_2
String query = "select * from active_tim where CLIENT_ID like '" + clientId + "' and TIM_TYPE_ID = " + timTypeId;

StringBuilder queryBuilder = new StringBuilder(
"SELECT * FROM active_tim WHERE CLIENT_ID = ? AND TIM_TYPE_ID = ?");
if (direction != null) {
query += " and DIRECTION = '" + direction + "'";
queryBuilder.append(" AND DIRECTION = ?");
}
queryBuilder.append(" AND MARKED_FOR_DELETION = '0'"); // exclude active tims marked for deletion
String query = queryBuilder.toString();
try (Connection connection = dbInteractions.getConnectionPool(); PreparedStatement ps = connection.prepareStatement(query)) {
ps.setString(1, clientId);
ps.setLong(2, timTypeId);
if (direction != null) {
ps.setString(3, direction);
}

query += " and MARKED_FOR_DELETION = '0'"; // exclude active tims marked for deletion

try (Connection connection = dbInteractions.getConnectionPool(); Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(query)) {
activeTims = getActiveTimFromRS(rs, false);
log.trace("Executing parameterized query to get active tims by client id and direction: \"{}\"", ps);
try (ResultSet rs = ps.executeQuery()) {
activeTims = getActiveTimFromRS(rs, false);
}
} catch (SQLException e) {
log.error("Error getting active tims by client id and direction", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(activeTims);
Expand All @@ -332,9 +344,17 @@ public ResponseEntity<List<ActiveTim>> GetActiveTimsByClientIdDirection(@PathVar
public ResponseEntity<List<ActiveTim>> GetBufferTimsByClientId(@PathVariable String clientId) {
List<ActiveTim> activeTims = new ArrayList<>();

String query = "select * from active_tim where CLIENT_ID like '" + clientId + "\\%BUFF_-%' ESCAPE '\\'";

try (Connection connection = dbInteractions.getConnectionPool(); Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(query)) {
// Use database concatenation (||) to append the LIKE pattern to the parameterized clientId.
// This follows best practices by keeping user input fully parameterized and separate from SQL literals.
// The PreparedStatement.setString() method automatically escapes special characters, so even if
// clientId contains malicious input like "'; DROP TABLE active_tim; --", it would be treated as
// a literal string pattern to match against CLIENT_ID values, not as executable SQL code.
String parameterizedQuery = "select * from active_tim where CLIENT_ID like ? || '%BUFF%'";
try (Connection connection = dbInteractions.getConnectionPool();
PreparedStatement statement = connection.prepareStatement(parameterizedQuery)
) {
statement.setString(1, clientId);
var rs = statement.executeQuery();
activeTims = getActiveTimFromRS(rs, false);
} catch (SQLException e) {
log.error("Error getting buffer tims by client id", e);
Expand Down Expand Up @@ -394,7 +414,13 @@ public ResponseEntity<Boolean> DeleteActiveTim(@PathVariable Long activeTimId) {
}

@RequestMapping(value = "/delete-ids", method = RequestMethod.DELETE, headers = "Accept=application/json")
public ResponseEntity<Boolean> DeleteActiveTimsById(@RequestBody List<Long> activeTimIds) {
public ResponseEntity<Boolean> deleteActiveTimsById(@RequestBody List<Long> activeTimIds) {
// Handle empty list case
if (activeTimIds == null || activeTimIds.isEmpty()) {
log.debug("No active tim IDs provided for deletion");
return ResponseEntity.ok(true); // Return success as there's nothing to delete
}

boolean deleteActiveTimResult = false;

StringBuilder deleteSQL = new StringBuilder("DELETE FROM ACTIVE_TIM WHERE ACTIVE_TIM_ID in (");
Expand All @@ -404,7 +430,8 @@ public ResponseEntity<Boolean> DeleteActiveTimsById(@RequestBody List<Long> acti
deleteSQL = new StringBuilder(deleteSQL.substring(0, deleteSQL.length() - 1));
deleteSQL.append(")");

try (Connection connection = dbInteractions.getConnectionPool(); PreparedStatement preparedStatement = connection.prepareStatement(deleteSQL.toString())) {
try (Connection connection = dbInteractions.getConnectionPool();
PreparedStatement preparedStatement = connection.prepareStatement(deleteSQL.toString())) {
for (int i = 0; i < activeTimIds.size(); i++) {
preparedStatement.setLong(i + 1, activeTimIds.get(i));
}
Expand All @@ -415,7 +442,7 @@ public ResponseEntity<Boolean> DeleteActiveTimsById(@RequestBody List<Long> acti
if (deleteActiveTimResult) {
log.info("Active Tims (active_tim_ids {}) are deleted!", activeTimIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
} else {
log.warn("Failed to delete Active Tims (active_tim_ids {}). They may not exist.", activeTimIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
log.warn("Failed to delete Active Tims (active_tim_ids {}). They may not exist.", Arrays.toString(activeTimIds.toArray()));
}

} catch (SQLException e) {
Expand Down Expand Up @@ -760,20 +787,20 @@ public ResponseEntity<Long> InsertActiveTim(@RequestBody ActiveTim activeTim) {
} else if (col.equals("DIRECTION")) {
sqlNullHandler.setStringOrNull(preparedStatement, fieldNum, activeTim.getDirection());
} else if (col.equals("TIM_START")) {
java.util.Date tim_start_date = utility.convertDate(activeTim.getStartDateTime());
java.util.Date tim_start_date = dateTimeHelper.convertDate(activeTim.getStartDateTime());
Timestamp tim_start_timestamp = new Timestamp(tim_start_date.getTime());
sqlNullHandler.setTimestampOrNull(preparedStatement, fieldNum, tim_start_timestamp);
} else if (col.equals("TIM_END")) {
if (activeTim.getEndDateTime() != null) {
java.util.Date tim_end_date = utility.convertDate(activeTim.getEndDateTime());
java.util.Date tim_end_date = dateTimeHelper.convertDate(activeTim.getEndDateTime());
Timestamp tim_end_timestamp = new Timestamp(tim_end_date.getTime());
sqlNullHandler.setTimestampOrNull(preparedStatement, fieldNum, tim_end_timestamp);
} else {
preparedStatement.setNull(fieldNum, java.sql.Types.TIMESTAMP);
}
} else if (col.equals("EXPIRATION_DATE")) {
if (activeTim.getExpirationDateTime() != null) {
java.util.Date tim_exp_date = utility.convertDate(activeTim.getExpirationDateTime());
java.util.Date tim_exp_date = dateTimeHelper.convertDate(activeTim.getExpirationDateTime());
Timestamp tim_exp_timestamp = new Timestamp(tim_exp_date.getTime());
sqlNullHandler.setTimestampOrNull(preparedStatement, fieldNum, tim_exp_timestamp);
} else {
Expand Down Expand Up @@ -955,7 +982,7 @@ public ResponseEntity<Boolean> UpdateExpiration(@PathVariable String packetID, @
updateStatement += ")";

try (Connection connection = dbInteractions.getConnectionPool(); PreparedStatement preparedStatement = connection.prepareStatement(updateStatement)) {
Date date = utility.convertDate(expDate);
Date date = dateTimeHelper.convertDate(expDate);
Timestamp expDateTimestamp = new Timestamp(date.getTime());
preparedStatement.setTimestamp(1, expDateTimestamp);// expDate comes in as MST from previously called function
// (GetMinExpiration)
Expand Down Expand Up @@ -993,7 +1020,7 @@ public ResponseEntity<String> GetMinExpiration(@PathVariable String packetID, @P
try (Connection connection = dbInteractions.getConnectionPool(); Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(query)) {
while (rs.next()) {
var tmpTs = rs.getTimestamp("MINSTART", UTCCalendar);
minStart = utility.timestampFormat.format(tmpTs);
minStart = utility.getTimestampFormat().format(tmpTs);
}
} catch (SQLException e) {
log.error("Error getting min expiration date for packetID: {}, expDate: {}", packetID, expDate, e);
Expand Down Expand Up @@ -1024,6 +1051,18 @@ public ResponseEntity<Boolean> MarkForDeletion(@PathVariable Long activeTimId) {
return ResponseEntity.ok(success);
}

@RequestMapping(value = "/get-active-planned-condition-tims", method = RequestMethod.GET)
public ResponseEntity<List<ActiveTim>> getActivePlannedConditionTims() throws SQLException {
String query = "select * from active_tim where client_id like '%planned%'";

try (Connection connection = dbInteractions.getConnectionPool(); PreparedStatement preparedStatement = connection.prepareStatement(query); ResultSet resultSet = preparedStatement.executeQuery()) {
return ResponseEntity.ok(getActiveTimFromRS(resultSet, false));
} catch(Exception e) {
log.error("Error getting active planned condition TIMs", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}

private TimUpdateModel buildTimUpdateModelFromResultSet(ResultSet rs) throws SQLException {
TimUpdateModel timUpdateModel = new TimUpdateModel();
timUpdateModel.setActiveTimId(rs.getLong("ACTIVE_TIM_ID"));
Expand Down
Loading
Loading