Skip to content

Add Async Operation Status Tracking OSGi Component #6641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 64 commits into
base: master
Choose a base branch
from

Conversation

roshanemoraes
Copy link

Current Limitation

WSO2 Identity Server (IS) performs various asynchronous operations, such as sharing applications and users across organizations, bulk user imports, and outbound provisioning. While error logs exist, they are not convenient for administrators to filter and analyze, making it difficult to identify failures and take corrective actions efficiently. Without such notifications, administrators may be unaware of failures or issues, leading to inconsistencies and delays in corrective actions.

Purpose

This pull request introduces a new OSGi component designed to enhance the WSO2 Carbon Identity framework's ability to manage and track the status of asynchronous operations. This component provides a comprehensive solution for monitoring the progress and outcomes of long-running tasks.

This component takes care of providing an overview of the most recent sharing status related with an asynchronous operation at the feature level.

Key Features and Changes:

Asynchronous Operation Status Tracking: Implements a robust and persistent mechanism for recording and monitoring the status of asynchronous operations within the WSO2 Carbon Identity framework.

Database Persistence:
Introduces two new database tables to persist operation statuses, ensuring data durability across system restarts:
IDN_ASYNC_OPERATION_STATUS and IDN_ASYNC_OPERATION_STATUS_UNIT.

OSGi Service Component:
Develops an OSGi service component, comprising the AsyncStatusMgtService interface and its implementation class AsyncStatusMgtServiceImpl, to manage the lifecycle and data access for asynchronous operation statuses.

The AsyncStatusMgtService interface defines the core methods for registering, updating, retrieving, and querying operation statuses.
The AsyncStatusMgtServiceImpl class provides the concrete implementation, handling database interactions and business logic.

Related Issues

wso2/product-is#22806 (comment)


public String getCode() {

return ERROR_PREFIX + code;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally the error prefix is added in the API layer

/**
* Enum for Error Message
*/
public static enum ErrorMessages {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move this to a separate class in the constant package

* Enum for Error Message
*/
public static enum ErrorMessages {
ERROR_CODE_INVALID_REQUEST_BODY("xx001", "Invalid request.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use, 65xxx for server errors and 60xxx for client errors for error codes

} catch (SQLException e) {
String errorMessage =
"Error while registering the asynchronous operation";
throw new RuntimeException(errorMessage, e);
Copy link
Contributor

@ashanthamara ashanthamara Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than throwing the base RuntimeException, let's create a specific runtime exception class specific for this service and use it here.

e.g.,

int rowsAffected = statement.executeUpdate();
LOGGER.info("Async Operation Registering Success. Rows affected: " + rowsAffected);

ResultSet generatedKeys = statement.getGeneratedKeys();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use try with resource for the ResultSet, or else make sure to close it

SQLConstants.OperationStatusTableColumns.IDN_OPERATION_POLICY, record.getOperationPolicy());

int rowsAffected = statement.executeUpdate();
LOGGER.info("Async Operation Registering Success. Rows affected: " + rowsAffected);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to make these logs as info ?

public List<ResponseOperationRecord> getOperationStatusByOperationTypeAndOperationSubjectId(
String operationType, String operationSubjectId) {

String sql =
Copy link
Contributor

@ashanthamara ashanthamara Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to the SQLConstants class.
Check other places

}

@Override
public List<ResponseOperationRecord> getOperationStatusByOperationSubjectTypeAndOperationSubjectIdAndOperationType(
Copy link
Contributor

@ashanthamara ashanthamara Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the method names simple, don't need to specify the arguments in the method name.

Check other places

}
} catch (SQLException e) {
String errorMessage = "Error fetching async operation status for subject: " + operationSubjectId;
LOGGER.info(errorMessage + e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LOGGER.info(errorMessage + e);
LOGGER.error(errorMessage + e);

Shouldn't this be an error ?

List<ResponseOperationRecord> operationRecords;
NamedJdbcTemplate namedJdbcTemplate = Utils.getNewTemplate();
try {
operationRecords = namedJdbcTemplate.executeQuery(sqlStmt,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to use this NamedJdbcTemplate in other methods as well.

Comment on lines 627 to 640
private FilterQueryBuilder buildFilterQuery(List<ExpressionNode> expressionNodes, String attributeUsedForCursor)
throws AsyncStatusMgtServerException {

FilterQueryBuilder filterQueryBuilder = new FilterQueryBuilder();
appendFilterQuery(expressionNodes, filterQueryBuilder, attributeUsedForCursor);
return filterQueryBuilder;
}

private void appendFilterQuery(List<ExpressionNode> expressionNodes, FilterQueryBuilder filterQueryBuilder,
String attributeUsedForCursor)
throws AsyncStatusMgtServerException {

int count = 1;
StringBuilder filter = new StringBuilder();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not do this in DAO layer. This should be handled in service layer.

Comment on lines 444 to 456
ResponseOperationRecord record = new ResponseOperationRecord();
record.setOperationId(resultSet.getString(1));
record.setCorrelationId(resultSet.getString(2));
record.setOperationType(resultSet.getString(3));
record.setOperationSubjectType(resultSet.getString(4));
record.setOperationSubjectId(resultSet.getString(5));
record.setResidentOrgId(resultSet.getString(6));
record.setInitiatorId(resultSet.getString(7));
record.setOperationStatus(resultSet.getString(8));
record.setOperationPolicy(resultSet.getString(9));
record.setCreatedTime(Timestamp.valueOf(resultSet.getString(10)));
record.setModifiedTime(Timestamp.valueOf(resultSet.getString(11)));
return record;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use object builder pattern for this ResponseOperationRecord class, this part can be further improved.

private boolean hasExistingRecords(Connection connection, String operationType, String operationSubjectId)
throws SQLException {

String checkSql = "SELECT COUNT(*) FROM IDN_ASYNC_OPERATION_STATUS " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
String checkSql = "SELECT COUNT(*) FROM IDN_ASYNC_OPERATION_STATUS " +
String checkSql = "SELECT 1 FROM IDN_ASYNC_OPERATION_STATUS " +

If we are checking only the existence, this can be more efficient rather than using count


import javax.sql.DataSource;

public class Utils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do not have common util class.
Split into separate util classes based on the functionality.

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
5.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants