diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml new file mode 100644 index 000000000..d01874f66 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml @@ -0,0 +1,222 @@ + + + + + + + org.wso2.carbon.identity.organization.management + identity-organization-management + 1.4.58-SNAPSHOT + ../../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service + WSO2 - Organization Resource Hierarchy Traverse Service + bundle + + + + org.wso2.carbon + org.wso2.carbon.utils + + + commons-collections.wso2 + commons-collections + + + org.ops4j.pax.logging + pax-logging-api + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.application.mgt + + + org.wso2.carbon.identity.organization.management.core + org.wso2.carbon.identity.organization.management.service + + + + + org.testng + testng + test + + + org.jacoco + org.jacoco.agent + runtime + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-inline + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.artifactId} + ${project.artifactId} + + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant, + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal, + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util + + + org.apache.commons.lang; version="${org.apache.commons.lang.imp.pkg.version.range}", + org.apache.commons.logging; version="${org.apache.commons.logging.imp.pkg.version.range}", + org.apache.commons.collections; version="${org.apache.commons.collections.imp.pkg.version.range}", + + org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", + org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}", + + org.wso2.carbon.identity.application.mgt; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.application.common; + version="${carbon.identity.package.import.version.range}", + + org.wso2.carbon.identity.organization.management.service; + version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}", + org.wso2.carbon.identity.organization.management.service.exception; + version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}", + org.wso2.carbon.identity.organization.management.service.util; + version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}" + + + !org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant, + !org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal, + !org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util, + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service, + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception, + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy; + version="${project.version}" + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + + ${argLine} + --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED + --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED + + + src/test/resources/testng.xml + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.class + + + + + default-prepare-agent + + prepare-agent + + + + default-prepare-agent-integration + + prepare-agent-integration + + + + default-report + + report + + + + default-report-integration + + report-integration + + + + default-check + + check + + + + + BUNDLE + + + COMPLEXITY + COVEREDRATIO + 0.60 + + + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java new file mode 100644 index 000000000..873276d05 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service; + +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.AggregationStrategy; + +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Provides a service interface to retrieve resources from an organization's hierarchy. + * Supports traversal of both organization and application hierarchies using customizable + * resource retrieval and aggregation strategies. + *

+ * The service is designed for extensibility, allowing clients to define their own retrieval logic + * and aggregation mechanisms via functional interfaces and strategy patterns. + */ +public interface OrgResourceResolverService { + + /** + * Retrieves resources by traversing the hierarchy of a given organization. + * + * @param organizationId The unique identifier of the organization. + * @param resourceRetriever A function that defines how to fetch a resource for a given organization ID. + * The function must return an {@link Optional} containing the resource if found, + * or an empty {@link Optional} if not. + * @param aggregationStrategy A strategy defining how to aggregate resources retrieved from + * different levels of the hierarchy. + * @param The type of the resource being retrieved and aggregated. + * @return An aggregated resource of type obtained from the organization hierarchy. + * @throws OrgResourceHierarchyTraverseException If any errors occur during resource retrieval + * or aggregation. + */ + T getResourcesFromOrgHierarchy(String organizationId, + Function> resourceRetriever, + AggregationStrategy aggregationStrategy) + throws OrgResourceHierarchyTraverseException; + + /** + * Retrieves resources by traversing the hierarchy of a given organization and application. + * + * @param organizationId The unique identifier of the organization. + * @param applicationId The unique identifier of the application within the organization. + * @param resourceRetriever A bi-function that defines how to fetch a resource based on the + * organization and application IDs. The function must return an + * {@link Optional} containing the resource if found, + * or an empty {@link Optional} if not. + * @param aggregationStrategy A strategy defining how to aggregate resources retrieved from + * different levels of the hierarchy. + * @param The type of the resource being retrieved and aggregated. + * @return An aggregated resource of type obtained from the organization and application hierarchy. + * @throws OrgResourceHierarchyTraverseException If any errors occur during resource retrieval + * or aggregation. + */ + T getResourcesFromOrgHierarchy(String organizationId, String applicationId, + BiFunction> resourceRetriever, + AggregationStrategy aggregationStrategy) + throws OrgResourceHierarchyTraverseException; +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java new file mode 100644 index 000000000..c1fb7c19f --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant.OrgResourceHierarchyTraverseConstants; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.AggregationStrategy; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util.OrgResourceHierarchyTraverseUtil; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Implementation of the OrgResourceResolverService interface, responsible for resolving resources within the + * given organization/ application hierarchy. + */ +public class OrgResourceResolverServiceImpl implements OrgResourceResolverService { + + @Override + public T getResourcesFromOrgHierarchy(String organizationId, Function> resourceRetriever, + AggregationStrategy aggregationStrategy) + throws OrgResourceHierarchyTraverseException { + + List organizationIds = getAncestorOrganizationsIds(organizationId); + return aggregationStrategy.aggregate(organizationIds, resourceRetriever); + } + + @Override + public T getResourcesFromOrgHierarchy(String organizationId, String applicationId, + BiFunction> resourceRetriever, + AggregationStrategy aggregationStrategy) + throws OrgResourceHierarchyTraverseException { + + try { + List organizationIds = getAncestorOrganizationsIds(organizationId); + + ApplicationManagementService applicationManagementService = getApplicationManagementService(); + Map ancestorAppIds = Collections.emptyMap(); + if (applicationId != null) { + ancestorAppIds = applicationManagementService.getAncestorAppIds(applicationId, organizationId); + } + + return aggregationStrategy.aggregate(organizationIds, ancestorAppIds, resourceRetriever); + } catch (IdentityApplicationManagementException e) { + throw OrgResourceHierarchyTraverseUtil.handleServerException( + OrgResourceHierarchyTraverseConstants.ErrorMessages + .ERROR_CODE_SERVER_ERROR_WHILE_RESOLVING_ANCESTOR_APPLICATIONS, + e, organizationId, applicationId); + } + } + + private List getAncestorOrganizationsIds(String organizationId) + throws OrgResourceHierarchyTraverseServerException { + + if (StringUtils.isBlank(organizationId)) { + throw OrgResourceHierarchyTraverseUtil.handleServerException( + OrgResourceHierarchyTraverseConstants.ErrorMessages.ERROR_CODE_EMPTY_ORGANIZATION_ID); + } + + try { + OrganizationManager organizationManager = OrgResourceHierarchyTraverseUtil.getOrganizationManager(); + List organizationIds = organizationManager.getAncestorOrganizationIds(organizationId); + if (CollectionUtils.isEmpty(organizationIds)) { + throw OrgResourceHierarchyTraverseUtil.handleServerException(OrgResourceHierarchyTraverseConstants + .ErrorMessages.ERROR_CODE_INVALID_ANCESTOR_ORGANIZATION_ID_LIST, + organizationId); + } + return organizationIds; + } catch (OrganizationManagementServerException e) { + throw OrgResourceHierarchyTraverseUtil.handleServerException( + OrgResourceHierarchyTraverseConstants.ErrorMessages + .ERROR_CODE_SERVER_ERROR_WHILE_RESOLVING_ANCESTOR_ORGANIZATIONS, e, organizationId); + } + } + + private ApplicationManagementService getApplicationManagementService() { + + return OrgResourceHierarchyTraverseServiceDataHolder.getInstance().getApplicationManagementService(); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java new file mode 100644 index 000000000..3c96c658c --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant; + +/** + * Constants defined for the organization resource hierarchy traverse service. + */ +public class OrgResourceHierarchyTraverseConstants { + + private static final String ORGANIZATION_RESOURCE_HIERARCHY_TRAVERSE_ERROR_CODE_PREFIX = "ORHT-"; + + /** + * Private constructor to prevent instantiation of this constant class. + */ + private OrgResourceHierarchyTraverseConstants() { + + } + + /** + * Enum which provides error codes and predefined error messages related to the traversal of + * the organization resource hierarchy. It ensures that error handling within the service is consistent + * and that detailed error descriptions are available for debugging and troubleshooting. + */ + public enum ErrorMessages { + + // Server errors. + ERROR_CODE_EMPTY_ORGANIZATION_ID( + "65001", + "Empty organization id.", + "Organization id cannot be null or empty."), + ERROR_CODE_INVALID_ANCESTOR_ORGANIZATION_ID_LIST( + "65002", + "Invalid ancestor organization id list.", + "The ancestor organization id list cannot be empty for the organization with id: %s. " + + "At least the organization itself should be included in the list."), + ERROR_CODE_SERVER_ERROR_WHILE_RESOLVING_ANCESTOR_ORGANIZATIONS( + "65003", + "Unable to resolve ancestor organizations.", + "Unexpected server error occurred " + + "while resolving ancestor organizations for organization with id: %s."), + ERROR_CODE_SERVER_ERROR_WHILE_RESOLVING_ANCESTOR_APPLICATIONS( + "65004", + "Unable to resolve ancestor applications.", + "Unexpected server error occurred while resolving ancestor applications for organization " + + "with id: %s for application with id: %s."); + + private final String code; + private final String message; + private final String description; + + /** + * Constructor for the ErrorMessages enum. + *

+ * This constructor is used to define each error message with a unique error code, a brief message, and + * a detailed description. + * + * @param code The unique error code for the message (prefixed with "ORHT-"). + * @param message A brief message describing the error. + * @param description A detailed description of the error, often including placeholders for dynamic data. + */ + ErrorMessages(String code, String message, String description) { + + this.code = code; + this.message = message; + this.description = description; + } + + /** + * Gets the unique error code for the error message. + *

+ * The error code is prefixed with "ORHT-" to ensure consistency in the error code system. + * + * @return The error code prefixed with "ORHT-". + */ + public String getCode() { + + return ORGANIZATION_RESOURCE_HIERARCHY_TRAVERSE_ERROR_CODE_PREFIX + code; + } + + /** + * Gets the brief message for the error. + *

+ * This message provides a short description of the error, usually without context-specific details. + * It is used for logging or displaying to the user as part of the error response. + * + * @return A brief message describing the error. + */ + public String getMessage() { + + return message; + } + + /** + * Gets the detailed description for the error message. + *

+ * This description provides a more detailed explanation of the error and often contains placeholders + * for dynamic data (e.g., organization or application IDs). It is typically used for logging or debugging. + * + * @return A detailed description of the error, including placeholders for dynamic data. + */ + public String getDescription() { + + return description; + } + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/NotImplementedException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/NotImplementedException.java new file mode 100644 index 000000000..c08cb203d --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/NotImplementedException.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; + +/** + * A custom runtime exception to indicate that a method or functionality is not yet implemented. + */ +public class NotImplementedException extends RuntimeException { + + private static final long serialVersionUID = 5117918786934518376L; + + /** + * Constructs a new NotImplementedException with no detail message or cause. + *

+ * This constructor is typically used when no additional information is required + * to explain why the exception is thrown. + */ + public NotImplementedException() { + + super(); + } + + /** + * Constructs a new NotImplementedException with a detailed message and the root cause. + * + * @param message A descriptive message explaining why this exception is thrown. + * @param cause The underlying cause of this exception. + */ + public NotImplementedException(String message, Throwable cause) { + + super(message, cause); + } + + /** + * Constructs a new NotImplementedException with a detailed message. + * + * @param message A descriptive message explaining why this exception is thrown. + */ + public NotImplementedException(String message) { + + super(message); + } + + /** + * Constructs a new NotImplementedException with the root cause. + * + * @param cause The underlying cause of this exception. + */ + public NotImplementedException(Throwable cause) { + + super(cause); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java new file mode 100644 index 000000000..30e459991 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Exception class for client-side errors during organization resource hierarchy traversal. + *

+ * This class handles exceptions where client-side errors occur, capturing error codes, messages, and descriptions + * to provide detailed context for troubleshooting. + */ +public class OrgResourceHierarchyTraverseClientException extends OrgResourceHierarchyTraverseException { + + private String[] messages; + + /** + * Constructs a new exception with an array of specified error messages. + * + * @param messages Detailed error messages. + */ + public OrgResourceHierarchyTraverseClientException(String[] messages) { + + super(Arrays.toString(messages)); + if (messages == null) { + return; + } + List msgList = new ArrayList<>(); + for (String msg : messages) { + if (!msg.trim().isEmpty()) { + msgList.add(msg); + } + } + this.messages = msgList.toArray(new String[0]); + } + + /** + * Constructs a new exception with the specified message. + * + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseClientException(String message) { + + super(message); + } + + /** + * Constructs a new exception with the specified message and cause. + * + * @param message Detailed message. + * @param e Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseClientException(String message, Throwable e) { + + super(message, e); + } + + /** + * Constructs a new exception with the specified error code and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseClientException(String errorCode, String message) { + + super(errorCode, message); + } + + /** + * Constructs a new exception with the specified error code, message and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseClientException(String errorCode, String message, Throwable cause) { + + super(errorCode, message, cause); + } + + /** + * Constructs a new exception with the specified error code, message and description. + * + * @param errorCode Error code. + * @param message Error message. + * @param description Error description. + */ + public OrgResourceHierarchyTraverseClientException(String errorCode, String message, String description) { + + super(errorCode, message, description); + } + + /** + * Constructs a new exception with the specified error code, message, description and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param description Error description. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseClientException(String errorCode, String message, String description, + Throwable cause) { + + super(errorCode, message, description, cause); + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", + justification = "Client exception error messages are internally generated from the server side.") + public String[] getMessages() { + + return messages; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java new file mode 100644 index 000000000..749498bec --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; + +/** + * Custom exception for errors encountered during organization resource hierarchy traversal. + *

+ * This exception captures detailed error information, including error codes, messages, descriptions, + * and causes, to assist in troubleshooting issues related to resolving ancestor organizations or applications. + */ +public class OrgResourceHierarchyTraverseException extends Exception { + + private String errorCode; + private String description; + + private static final long serialVersionUID = 5967152066669023668L; + + /** + * Constructs a new exception with the specified message. + * + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseException(String message) { + + super(message); + } + + /** + * Constructs a new exception with the specified message and cause. + * + * @param message Detailed message. + * @param e Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseException(String message, Throwable e) { + + super(message, e); + } + + /** + * Constructs a new exception with the specified error code and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseException(String errorCode, String message) { + + super(message); + this.errorCode = errorCode; + } + + /** + * Constructs a new exception with the specified error code, message and description. + * + * @param errorCode Error code. + * @param message Error message. + * @param description Error description. + */ + public OrgResourceHierarchyTraverseException(String errorCode, String message, String description) { + + super(message); + this.errorCode = errorCode; + this.description = description; + } + + /** + * Constructs a new exception with the specified error code, message and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseException(String errorCode, String message, Throwable cause) { + + super(message, cause); + this.errorCode = errorCode; + } + + /** + * Constructs a new exception with the specified error code, message, description and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param description Error description. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseException(String errorCode, String message, String description, + Throwable cause) { + + super(message, cause); + this.errorCode = errorCode; + this.description = description; + } + + /** + * Returns the error code. + * + * @return Error code. + */ + public String getErrorCode() { + + return errorCode; + } + + /** + * Returns the error description. + * + * @return Error description. + */ + public String getDescription() { + + return description; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java new file mode 100644 index 000000000..2b2f7ecef --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; + +/** + * Exception class for server-side errors during organization resource hierarchy traversal. + *

+ * This class handles exceptions related to server-side failures, including error codes, messages, + * descriptions, and causes, to provide detailed information for debugging. + */ +public class OrgResourceHierarchyTraverseServerException extends OrgResourceHierarchyTraverseException { + + /** + * Constructs a new exception with the specified message. + * + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseServerException(String message) { + + super(message); + } + + /** + * Constructs a new exception with the specified message and cause. + * + * @param message Detailed message. + * @param e Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseServerException(String message, Throwable e) { + + super(message, e); + } + + /** + * Constructs a new exception with the specified error code and message. + * + * @param errorCode Error code. + * @param message Detailed message. + */ + public OrgResourceHierarchyTraverseServerException(String errorCode, String message) { + + super(errorCode, message); + } + + /** + * Constructs a new exception with the specified error code, message and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseServerException(String errorCode, String message, Throwable cause) { + + super(errorCode, message, cause); + } + + /** + * Constructs a new exception with the specified error code, message and description. + * + * @param errorCode Error code. + * @param message Error message. + * @param description Error description. + */ + public OrgResourceHierarchyTraverseServerException(String errorCode, String message, String description) { + + super(errorCode, message, description); + } + + /** + * Constructs a new exception with the specified error code, message, description and cause. + * + * @param errorCode Error code. + * @param message Detailed message. + * @param description Error description. + * @param cause Cause as {@link Throwable}. + */ + public OrgResourceHierarchyTraverseServerException(String errorCode, String message, String description, + Throwable cause) { + + super(errorCode, message, description, cause); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java new file mode 100644 index 000000000..022cc1831 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.OrgResourceResolverService; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.OrgResourceResolverServiceImpl; + +/** + * OSGi component responsible for managing the activation and deactivation of the organization resource hierarchy + * traverse service. + *

+ * It manages dynamic references to necessary services required by {@link OrgResourceResolverService} as well. + */ +@Component( + name = "org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service", + immediate = true) +public class OrgResourceHierarchyTraverseServiceComponent { + + private static final Log LOG = LogFactory.getLog(OrgResourceHierarchyTraverseServiceComponent.class); + + /** + * Activates the OSGi component by registering the {@link OrgResourceResolverService} service. + * This method is called when the component is activated in the OSGi environment. + * + * @param context The ComponentContext instance that provides the OSGi environment context. + */ + @Activate + protected void activate(ComponentContext context) { + + try { + BundleContext bundleContext = context.getBundleContext(); + bundleContext.registerService(OrgResourceResolverService.class.getName(), + new OrgResourceResolverServiceImpl(), null); + if (LOG.isDebugEnabled()) { + LOG.debug("OrgResourceResolverService bundle is activated successfully."); + } + } catch (Exception e) { + LOG.error("Error while activating OrgResourceResolverService bundle.", e); + } + } + + /** + * Deactivates the OSGi component by cleaning up resources and logging the deactivation. + * This method is called when the component is deactivated in the OSGi environment. + * + * @param context The ComponentContext instance that provides the OSGi environment context. + */ + @Deactivate + protected void deactivate(ComponentContext context) { + + if (LOG.isDebugEnabled()) { + LOG.debug("OrgResourceResolverService bundle is deactivated"); + } + } + + /** + * Sets the OrganizationManager instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param organizationManager The OrganizationManager instance to be assigned. + */ + @Reference(name = "org.wso2.carbon.identity.organization.management.service", + service = OrganizationManager.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetOrganizationManager") + protected void setOrganizationManager(OrganizationManager organizationManager) { + + OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setOrganizationManager(organizationManager); + LOG.debug("OrganizationManager set in OrgResourceManagementServiceComponent bundle."); + } + + /** + * Unsets the OrganizationManager instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param organizationManager The OrganizationManager instance to be removed. + */ + protected void unsetOrganizationManager(OrganizationManager organizationManager) { + + OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setOrganizationManager(null); + LOG.debug("OrganizationManager unset in OrgResourceManagementServiceComponent bundle."); + } + + /** + * Sets the ApplicationManagementService instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param applicationManagementService The ApplicationManagementService instance to be assigned. + */ + @Reference( + name = "org.wso2.carbon.identity.application.mgt", + service = ApplicationManagementService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetApplicationManagementService") + protected void setApplicationManagementService(ApplicationManagementService applicationManagementService) { + + OrgResourceHierarchyTraverseServiceDataHolder.getInstance() + .setApplicationManagementService(applicationManagementService); + LOG.debug("ApplicationManagementService set in OrgResourceManagementServiceComponent bundle."); + } + + /** + * Unsets the ApplicationManagementService instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param applicationManagementService The ApplicationManagementService instance to be removed. + */ + protected void unsetApplicationManagementService(ApplicationManagementService applicationManagementService) { + + OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setApplicationManagementService(null); + LOG.debug("ApplicationManagementService unset in OrgResourceManagementServiceComponent bundle."); + } +} + diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java new file mode 100644 index 000000000..0b4fd6686 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal; + +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; + +/** + * Singleton class that serves as a centralized data holder for key service instances used in the organization + * resource hierarchy traversal process. + */ +public class OrgResourceHierarchyTraverseServiceDataHolder { + + private static final OrgResourceHierarchyTraverseServiceDataHolder INSTANCE = + new OrgResourceHierarchyTraverseServiceDataHolder(); + + private OrganizationManager organizationManager; + private ApplicationManagementService applicationManagementService; + + private OrgResourceHierarchyTraverseServiceDataHolder() { + + } + + /** + * Retrieves the Singleton instance of the OrgResourceHierarchyTraverseServiceDataHolder class. + * + * @return The singleton instance of OrgResourceHierarchyTraverseServiceDataHolder. + */ + public static OrgResourceHierarchyTraverseServiceDataHolder getInstance() { + + return INSTANCE; + } + + /** + * Retrieves the current instance of the OrganizationManager. + * + * @return The current OrganizationManager instance that manages organizational data. + */ + public OrganizationManager getOrganizationManager() { + + return organizationManager; + } + + /** + * Sets the OrganizationManager instance. + * + * @param organizationManager The OrganizationManager instance to be assigned. + */ + public void setOrganizationManager(OrganizationManager organizationManager) { + + this.organizationManager = organizationManager; + } + + /** + * Retrieves the current instance of the ApplicationManagementService. + * + * @return The current ApplicationManagementService instance responsible for managing applications. + */ + public ApplicationManagementService getApplicationManagementService() { + + return applicationManagementService; + } + + /** + * Sets the ApplicationManagementService instance. + * + * @param applicationManagementService The ApplicationManagementService instance to be set. + */ + public void setApplicationManagementService(ApplicationManagementService applicationManagementService) { + + this.applicationManagementService = applicationManagementService; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java new file mode 100644 index 000000000..ef0798a84 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy; + +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.NotImplementedException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Defines an interface for a strategy used for aggregating resources retrieved from hierarchical structures, + * such as organization and application hierarchies. This interface provides default + * methods that can be overridden to implement specific aggregation logic. + *

+ * Implementations should specify how resources are combined and may use functional + * interfaces for flexible retrieval mechanisms. + * + * @param The type of the resource to be aggregated. + */ +public interface AggregationStrategy { + + /** + * Aggregates resources resolved from an organization's hierarchical structure. + *

+ * This method provides a default implementation that throws a + * {@link NotImplementedException}. Subclasses must override this method to define + * specific aggregation logic for organization hierarchies. + * + * @param organizationHierarchy A list representing the organization hierarchy, + * where the first element is the root organization + * and subsequent elements represent child organizations. + * @param resourceRetriever A function that retrieves a resource given an organization ID. + * Returns an {@link Optional} containing the resource, or empty + * if no resource is found for the given ID. + * @return The aggregated resource of type . + * @throws OrgResourceHierarchyTraverseException If any error occurs during resource + * retrieval or aggregation. + */ + default T aggregate(List organizationHierarchy, Function> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + throw new NotImplementedException("aggregate method is not implemented in " + this.getClass()); + } + + /** + * Aggregates resources resolved from an organization's and application's hierarchical structure. + *

+ * This method provides a default implementation that throws a + * {@link NotImplementedException}. Subclasses must override this method to define + * specific aggregation logic for combined organization and application hierarchies. + * + * @param organizationHierarchy A list representing the organization hierarchy, + * where the first element is the root organization + * and subsequent elements represent child organizations. + * @param applicationHierarchy A map representing the application hierarchy, where keys + * are organization IDs, and values are application-specific + * details or IDs for each organization. + * @param resourceRetriever A bi-function that retrieves a resource based on both an + * organization ID and an application ID. Returns an {@link Optional} + * containing the resource, or empty if no resource is found. + * @return The aggregated resource of type . + * @throws OrgResourceHierarchyTraverseException If any error occurs during resource + * retrieval or aggregation. + */ + default T aggregate(List organizationHierarchy, Map applicationHierarchy, + BiFunction> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + throw new NotImplementedException("aggregate method is not implemented in " + this.getClass()); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java new file mode 100644 index 000000000..efede0cb7 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy; + +import org.apache.commons.collections.CollectionUtils; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util.OrgResourceHierarchyTraverseUtil; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Aggregation strategy that can be used to traverse the organization hierarchy and retrieve the first resource found. + * This strategy is commonly applied when multiple resources might exist at different levels of the hierarchy, + * and only the first one encountered at the bottom of the hierarchy needs to be returned. + * + * @param The type of the resource being retrieved from the organization/ application hierarchy. + */ +public class FirstFoundAggregationStrategy implements AggregationStrategy { + + @Override + public T aggregate(List organizationHierarchy, Function> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + if (CollectionUtils.isEmpty(organizationHierarchy)) { + return null; + } + + for (String orgId : organizationHierarchy) { + if (OrgResourceHierarchyTraverseUtil.isMinOrgHierarchyDepthReached(orgId)) { + break; + } + + Optional resource = resourceRetriever.apply(orgId); + if (resource.isPresent()) { + return resource.get(); + } + } + return null; + } + + @Override + public T aggregate(List organizationHierarchy, Map applicationHierarchy, + BiFunction> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + if (CollectionUtils.isEmpty(organizationHierarchy)) { + return null; + } + + for (String orgId : organizationHierarchy) { + if (OrgResourceHierarchyTraverseUtil.isMinOrgHierarchyDepthReached(orgId)) { + break; + } + + String appId = applicationHierarchy.get(orgId); + Optional resource = resourceRetriever.apply(orgId, appId); + if (resource.isPresent()) { + return resource.get(); + } + } + return null; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java new file mode 100644 index 000000000..2d1ad9372 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy; + +import org.apache.commons.collections.CollectionUtils; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util.OrgResourceHierarchyTraverseUtil; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Aggregation strategy to merge all resources in the organization hierarchy using the specified + * resource merger function. + *

+ * This strategy traverses the hierarchy and applies the provided function + * to combine the resources at each level. It ensures that the resources are merged according to the + * logic defined by the merger function, which could involve combining attributes, performing calculations, or + * resolving conflicts between resources. + * + * @param The type of the resources being merged in the organization/ application hierarchy. + */ +public class MergeAllAggregationStrategy implements AggregationStrategy { + + private final BiFunction resourceMerger; + + /** + * Constructor to initialize the aggregation strategy with the resource merger function. + * + * @param resourceMerger Resource merger function. + */ + public MergeAllAggregationStrategy(BiFunction resourceMerger) { + + this.resourceMerger = resourceMerger; + } + + @Override + public T aggregate(List organizationHierarchy, Function> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + T aggregatedResource = null; + if (CollectionUtils.isEmpty(organizationHierarchy)) { + return aggregatedResource; + } + + for (String orgId : organizationHierarchy) { + if (OrgResourceHierarchyTraverseUtil.isMinOrgHierarchyDepthReached(orgId)) { + break; + } + + Optional resource = resourceRetriever.apply(orgId); + if (resource.isPresent()) { + if (aggregatedResource == null) { + aggregatedResource = resource.get(); + } else { + aggregatedResource = resourceMerger.apply(aggregatedResource, resource.get()); + } + } + } + return aggregatedResource; + } + + @Override + public T aggregate(List organizationHierarchy, Map applicationHierarchy, + BiFunction> resourceRetriever) + throws OrgResourceHierarchyTraverseException { + + T aggregatedResource = null; + if (CollectionUtils.isEmpty(organizationHierarchy)) { + return aggregatedResource; + } + + for (String orgId : organizationHierarchy) { + if (OrgResourceHierarchyTraverseUtil.isMinOrgHierarchyDepthReached(orgId)) { + break; + } + + String appId = applicationHierarchy.get(orgId); + Optional resource = resourceRetriever.apply(orgId, appId); + if (resource.isPresent()) { + if (aggregatedResource == null) { + aggregatedResource = resource.get(); + } else { + aggregatedResource = resourceMerger.apply(aggregatedResource, resource.get()); + } + } + } + return aggregatedResource; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java new file mode 100644 index 000000000..7324900a5 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.util; + +import org.apache.commons.lang.ArrayUtils; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; +import org.wso2.carbon.identity.organization.management.service.util.Utils; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant.OrgResourceHierarchyTraverseConstants; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; + +/** + * Utility class for the Organization Resource Hierarchy Traverse Service. + *

+ * This class provides helper methods to interact with the organization hierarchy and manage traversal logic, + * such as verifying the hierarchy depth and handling server-side exceptions. + */ +public class OrgResourceHierarchyTraverseUtil { + + /** + * Private constructor to prevent instantiation of the utility class. + */ + private OrgResourceHierarchyTraverseUtil() { + + } + + /** + * Retrieve the organization manager instance. + * + * @return The {@link OrganizationManager} instance used to manage organizations. + */ + public static OrganizationManager getOrganizationManager() { + + return OrgResourceHierarchyTraverseServiceDataHolder.getInstance().getOrganizationManager(); + } + + /** + * Verify if the organization hierarchy depth has reached the minimum required level. + *

+ * This method obtains the depth of the specified organization in the hierarchy and compares it + * with the configured minimum depth. An exception is thrown if an error occurs during the depth calculation. + * + * @param orgId The ID of the organization to check. + * @return {@code true} if the hierarchy depth is less than the minimum required depth, {@code false} otherwise. + * @throws OrgResourceHierarchyTraverseServerException If an error occurs while retrieving the organization's depth. + */ + public static boolean isMinOrgHierarchyDepthReached(String orgId) throws + OrgResourceHierarchyTraverseServerException { + + int minHierarchyDepth = Utils.getSubOrgStartLevel() - 1; + try { + int depthInHierarchy = getOrganizationManager().getOrganizationDepthInHierarchy(orgId); + return depthInHierarchy < minHierarchyDepth; + } catch (OrganizationManagementServerException e) { + throw new OrgResourceHierarchyTraverseServerException( + "Error occurred while getting the hierarchy depth of the organization: " + orgId, e); + } + } + + /** + * Create an {@link OrgResourceHierarchyTraverseServerException} to handle server-side errors. + *

+ * This method formats the error description using the provided data, if applicable, and constructs + * a custom exception for consistent error handling in the service. + * + * @param error The error enumeration containing predefined error messages and codes. + * @param data Optional data to format the error message description. + * @return An {@link OrgResourceHierarchyTraverseServerException} with the formatted error details. + */ + public static OrgResourceHierarchyTraverseServerException handleServerException( + OrgResourceHierarchyTraverseConstants.ErrorMessages error, String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, data); + } + return new OrgResourceHierarchyTraverseServerException(error.getMessage(), description, error.getCode()); + } + + /** + * Create an {@link OrgResourceHierarchyTraverseServerException} to handle server-side errors. + *

+ * This method formats the error description using the provided data, if applicable, and constructs + * a custom exception including the underlying cause of the error, for consistent error handling in the service. + * + * @param error The error enumeration containing predefined error messages and codes. + * @param e The underlying cause of the error. + * @param data Optional data to format the error message description. + * @return An {@link OrgResourceHierarchyTraverseServerException} with the formatted error details. + */ + public static OrgResourceHierarchyTraverseServerException handleServerException( + OrgResourceHierarchyTraverseConstants.ErrorMessages error, Throwable e, String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, data); + } + return new OrgResourceHierarchyTraverseServerException(error.getMessage(), description, error.getCode(), e); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceTest.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceTest.java new file mode 100644 index 000000000..311ed269c --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceTest.java @@ -0,0 +1,688 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service; + +import org.mockito.Mock; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.MockResourceManagementService; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model.MockResource; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.AggregationStrategy; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.FirstFoundAggregationStrategy; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.MergeAllAggregationStrategy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertThrows; + +/** + * Unit tests for the OrgResourceResolverService. + */ +public class OrgResourceResolverServiceTest { + + private static final String ROOT_ORG_ID = "10084a8d-113f-4211-a0d5-efe36b082211"; + private static final String ROOT_APP_ID = "1ee981ab-64e7-435c-ab91-e8d1e0a13b2c"; + private static final String L1_ORG_ID = "93d996f9-a5ba-4275-a52b-adaad9eba869"; + private static final String L1_APP_ID = "619d2cb1-174d-4d38-af3b-99c532dddb00"; + private static final String L2_ORG_ID = "30b701c6-e309-4241-b047-0c299c45d1a0"; + private static final String L2_APP_ID = "d04d48db-8c5a-437a-9458-4352b84db621"; + private static final String INVALID_ORG_ID = "invalid-org-id"; + private static final String INVALID_APP_ID = "invalid-app-id"; + + private OrgResourceResolverService orgResourceResolverService; + private MockResourceManagementService mockResourceManagementService; + private AggregationStrategy firstFoundAggregationStrategy; + private AggregationStrategy mergeAllAggregationStrategy; + + @Mock + OrganizationManager organizationManager; + + @Mock + ApplicationManagementService applicationManagementService; + + /** + * Initializes test data and mock services before the test class is run. + * This method sets up necessary services and aggregation strategies required for the tests. + */ + @BeforeClass + public void init() { + + // Open mock objects for the current test instance. + openMocks(this); + + // Set the OrganizationManager and ApplicationManagementService to the data holder for use in tests + OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setOrganizationManager(organizationManager); + OrgResourceHierarchyTraverseServiceDataHolder.getInstance() + .setApplicationManagementService(applicationManagementService); + + // Initialize the aggregation strategies with the appropriate strategy types. + firstFoundAggregationStrategy = new FirstFoundAggregationStrategy<>(); + mergeAllAggregationStrategy = new MergeAllAggregationStrategy<>(this::resourceMerger); + } + + /** + * Sets up mocks for individual test cases by initializing mock services and resource management. + * This method runs before each test method to ensure the environment is ready for testing specific scenarios. + */ + @BeforeMethod + public void setUp() throws Exception { + + // Mock responses for the organization manager with different ancestor organization chains. + mockAncestorOrganizationRetrieval(Arrays.asList(L2_ORG_ID, L1_ORG_ID, ROOT_ORG_ID)); + mockAncestorOrganizationRetrieval(Arrays.asList(L1_ORG_ID, ROOT_ORG_ID)); + mockAncestorOrganizationRetrieval(Collections.singletonList(ROOT_ORG_ID)); + + // Mock responses for the application management service with corresponding application ancestor data. + mockAncestorApplicationRetrieval(Arrays.asList(L2_ORG_ID, L1_ORG_ID, ROOT_ORG_ID), + Arrays.asList(L2_APP_ID, L1_APP_ID, ROOT_APP_ID)); + mockAncestorApplicationRetrieval(Arrays.asList(L1_ORG_ID, ROOT_ORG_ID), + Arrays.asList(L1_APP_ID, ROOT_APP_ID)); + mockAncestorApplicationRetrieval(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + // Initialize the mock resource management service used in tests. + mockResourceManagementService = new MockResourceManagementService(); + + // Instantiate the OrgResourceResolverService for testing. + orgResourceResolverService = new OrgResourceResolverServiceImpl(); + } + + /** + * Resets mock services after each test method to ensure a clean state for subsequent tests. + */ + @AfterMethod + public void tearDown() { + + // Reset the mock services to their default state after each test. + reset(organizationManager); + reset(applicationManagementService); + } + + @DataProvider(name = "AggregationStrategyDataProvider") + public Object[][] provideAggregationStrategies() { + + return new Object[][]{ + {firstFoundAggregationStrategy}, + {mergeAllAggregationStrategy} + }; + } + + /** + * Tests the behavior of the OrgResourceResolverService when no resources are available in the + * organization hierarchy. + *

+ * This test ensures that the resolver correctly returns null when no resources are found at any organization level. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenNoResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertNull(resolvedRootResource); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertNull(resolvedL1Resource); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertNull(resolvedL2Resource); + } + + /** + * Tests the behavior of the OrgResourceResolverService when an organization-level resource is available at the + * root organization level. + *

+ * This test ensures that the resolver correctly returns the resource for the root organization and propagates + * the resolution to its child organizations (L1 and L2) using the given aggregation strategy. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenRootResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resource for root organization into the mock resource management service. + List createdOrgResource = addOrgResources(Collections.singletonList(ROOT_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResource.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResource.get(0)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResource.get(0)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when an organization-level resource is available at the + * L1 organization. + *

+ * This test ensures that when a resource is available at the L1 level, it is correctly resolved for + * L1, and L2 organizations according to the provided aggregation strategy. At the root level, + * its own resource should be returned. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenL1OrgResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResources.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResources.get(1)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResources.get(1)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when an organization-level resource is available at the + * L2 organization. + *

+ * This test ensures that when a resource is available at the L2 level, it is correctly resolved for the + * L2 organization based on the provided aggregation strategy. + * At the root and L1 level, their own resource should be returned. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenL2OrgResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L2, L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID, L2_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResources.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResources.get(1)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResources.get(2)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when no application-level or organization-level resources + * are available in the hierarchy. + *

+ * This test ensures that when no resources are available at the organization or application levels, + * the resolver correctly returns null. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenNoResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertNull(resolvedRootAppResource); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertNull(resolvedL1AppResource); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertNull(resolvedL2AppResource); + } + + /** + * Tests the behavior of the OrgResourceResolverService when resources are available at both root organization + * and/ or root application levels in the organization hierarchy. + *

+ * This test verifies that when resources are available for the root level, they are correctly resolved at the + * organization and application levels across all organization levels (root, L1, and L2). + * + * @param aggregationStrategy The aggregation strategy used to resolve resources at different levels. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenRootResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resource for root organization into the mock resource management service. + List createdOrgResource = addOrgResources(Collections.singletonList(ROOT_ORG_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdOrgResource.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdOrgResource.get(0)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResource.get(0)); + + // Add app-level resource for root organization into the mock resource management service. + List createdAppResource = addAppResources(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResource.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResource.get(0)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResource.get(0)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when organization-level and/ or application-level resources + * are available for both root and L1 organizations in the hierarchy. + *

+ * This test ensures when resources are available for the L1 level, they are correctly resolved at the + * organization and application levels across both organization levels, L1, and L2. At the root level, + * its own resource should be returned. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources at different levels. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenL1ResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID)); + // Add app-level resources for root organization into the mock resource management service. + List createdAppResources = addAppResources(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdOrgResources.get(1)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResources.get(1)); + + // Add app-level resources for L1 organization into the mock resource management service. + createdAppResources.addAll(addAppResources(Collections.singletonList(L1_ORG_ID), + Collections.singletonList(L1_APP_ID))); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResources.get(1)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when organization-level and/ or application-level resources + * are available for all organization levels in the hierarchy. + *

+ * This test ensures that when a resource is available at the L2 level, it is correctly resolved for the + * L2 organization based on the provided aggregation strategy. At the root and L1 level, their own resources + * should be returned. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources at different levels. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenL2ResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L2, L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID, L2_ORG_ID)); + // Add app-level resources for L1 and root organizations into the mock resource management service. + List createdAppResources = addAppResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID), + Arrays.asList(ROOT_APP_ID, L1_APP_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResources.get(2)); + + // Add app-level resources for L2 organization into the mock resource management service. + createdAppResources.addAll(addAppResources(Collections.singletonList(L2_ORG_ID), + Collections.singletonList(L2_APP_ID))); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResources.get(2)); + } + + @DataProvider(name = "provideAggregationStrategiesForOrgLevelResolverWithInvalidInput") + public Object[][] provideAggregationStrategiesForOrgLevelResolverWithInvalidInput() { + + List invalidOrgIds = Arrays.asList(null, "", INVALID_ORG_ID); + + return new Object[][]{ + {firstFoundAggregationStrategy, invalidOrgIds}, + {mergeAllAggregationStrategy, invalidOrgIds}, + }; + } + + /** + * Tests the behavior of the OrgResourceResolverService when provided with invalid organization IDs. + *

+ * This test ensures that when invalid organization IDs are passed to the resolver, it correctly returns null, + * indicating that no resources are found for the invalid input. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources at different levels. + * @param invalidOrgIds A list of invalid organization IDs to test the resolver's behavior. + * @throws Exception If an error occurs during resolution. + */ + @Test(dataProvider = "provideAggregationStrategiesForOrgLevelResolverWithInvalidInput") + public void testGetOrgLevelResourcesFromOrgHierarchyForInvalidInput( + AggregationStrategy aggregationStrategy, List invalidOrgIds) throws Exception { + + for (String orgId : invalidOrgIds) { + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, orgId)); + } + } + + @DataProvider(name = "provideAggregationStrategiesForAppLevelResolverWithInvalidInput") + public Object[][] provideAggregationStrategiesForAppLevelResolverWithInvalidInput() { + + List invalidOrgIds = Arrays.asList(null, "", INVALID_ORG_ID); + List invalidAppIds = Arrays.asList(null, "", INVALID_APP_ID); + + return new Object[][]{ + {firstFoundAggregationStrategy, invalidOrgIds, invalidAppIds}, + {mergeAllAggregationStrategy, invalidOrgIds, invalidAppIds}, + }; + } + + /** + * Tests the behavior of the OrgResourceResolverService when provided with invalid organization and application IDs. + *

+ * This test verifies that the resolver correctly handles invalid input combinations by returning null, + * ensuring robustness and appropriate error handling in edge cases. The test covers scenarios where: + * - The organization ID is invalid. + * - The application ID is invalid. + * - Both the organization and application IDs are invalid. + * + * @param aggregationStrategy The aggregation strategy used to resolve resources across the hierarchy. + * @param invalidOrgIds A list of invalid organization IDs to test the resolver's behavior. + * @param invalidAppIds A list of invalid application IDs to test the resolver's behavior. + * @throws Exception If an error occurs during resolution. + */ + @Test(dataProvider = "provideAggregationStrategiesForAppLevelResolverWithInvalidInput") + public void testGetAppLevelResourcesFromOrgHierarchyForInvalidInput( + AggregationStrategy aggregationStrategy, List invalidOrgIds, + List invalidAppIds) throws Exception { + + for (String invalidOrgId : invalidOrgIds) { + for (String invalidAppId : invalidAppIds) { + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, invalidOrgId, invalidAppId)); + } + } + } + + /** + * Tests the behavior of the OrgResourceResolverService when server-side errors occur during + * organizational hierarchy traversal. + *

+ * This test simulates server-side exceptions thrown by the `organizationManager` during the following scenarios: + * - Fetching the depth of an organization within the hierarchy. + * - Retrieving ancestor organization IDs. + * + * @param aggregationStrategy The aggregation strategy used for resolving resources. + * @throws Exception If an unexpected error occurs. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenServerErrorOccurs( + AggregationStrategy aggregationStrategy) throws Exception { + + when(organizationManager.getOrganizationDepthInHierarchy(anyString())) + .thenThrow(OrganizationManagementServerException.class); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID)); + + when(organizationManager.getAncestorOrganizationIds(anyString())) + .thenThrow(OrganizationManagementServerException.class); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID)); + } + + /** + * Tests the behavior of the OrgResourceResolverService when server-side errors occur during + * application hierarchy traversal within an organization. + *

+ * This test simulates server-side exceptions thrown by the following: + * - The `applicationManagementService` while retrieving ancestor application IDs. + * - The `organizationManager` while retrieving ancestor organization IDs. + * + * @param aggregationStrategy The aggregation strategy used for resolving resources. + * @throws Exception If an unexpected error occurs. + */ + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenServerErrorOccurs( + AggregationStrategy aggregationStrategy) throws Exception { + + when(applicationManagementService.getAncestorAppIds(anyString(), anyString())) + .thenThrow(IdentityApplicationManagementException.class); + + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID)); + + when(organizationManager.getAncestorOrganizationIds(anyString())) + .thenThrow(OrganizationManagementServerException.class); + + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID)); + } + + /** + * Mock the retrieval of ancestor organization IDs. + * + * @param orgIds Organization IDs. + * @throws OrganizationManagementException If an error occurs while retrieving ancestor organization IDs. + */ + private void mockAncestorOrganizationRetrieval(List orgIds) + throws OrganizationManagementException { + + List ancestorOrganizationIds = new ArrayList<>(orgIds); + when(organizationManager.getAncestorOrganizationIds(orgIds.get(0))).thenReturn(ancestorOrganizationIds); + } + + /** + * Mock the retrieval of ancestor application IDs. + * + * @param orgIds Organization IDs. + * @param appIds Application IDs. + * @throws IdentityApplicationManagementException If an error occurs while retrieving ancestor application IDs. + */ + private void mockAncestorApplicationRetrieval(List orgIds, List appIds) + throws IdentityApplicationManagementException { + + Map ancestorAppIds = new HashMap<>(); + for (String orgId : orgIds) { + ancestorAppIds.put(orgId, appIds.get(orgIds.indexOf(orgId))); + } + + when(applicationManagementService.getAncestorAppIds(appIds.get(0), orgIds.get(0))).thenReturn(ancestorAppIds); + } + + /** + * Add organization resources to the mock resource management service. + * + * @param orgIds Organization IDs. + * @return List of created organization resources. + */ + private List addOrgResources(List orgIds) { + + List createdOrgResources = new ArrayList<>(); + int resourceId = 1; + for (String orgId : orgIds) { + MockResource orgResource = new MockResource(resourceId++, orgId + "Org Resource", orgId); + mockResourceManagementService.addOrgResource(orgResource); + createdOrgResources.add(orgResource); + } + return createdOrgResources; + } + + /** + * Add application resources to the mock resource management service. + * + * @param orgIds Organization IDs. + * @param appIds Application IDs. + * @return List of created application resources. + */ + private List addAppResources(List orgIds, List appIds) { + + List createdAppResources = new ArrayList<>(); + int resourceId = 1; + for (String orgId : orgIds) { + MockResource appResource = + new MockResource(resourceId++, orgId + "App Resource", orgId, appIds.get(orgIds.indexOf(orgId))); + mockResourceManagementService.addAppResource(appResource); + createdAppResources.add(appResource); + } + return createdAppResources; + } + + /** + * Resource resolver function used for testing MergeAllAggregationStrategy. + * + * @param aggregatedResource Aggregated resource. + * @param newResource New resource. + * @return Merged resource. + */ + private MockResource resourceMerger(MockResource aggregatedResource, MockResource newResource) { + + if (aggregatedResource == null) { + return newResource; + } + return aggregatedResource; + } + + /** + * Invoke the organization level resource resolver. + * + * @param aggregationStrategy Aggregation strategy. + * @param organizationId Organization ID. + * @return Resolved resource. + * @throws OrgResourceHierarchyTraverseException If an error occurs while resolving the resource. + */ + private MockResource invokeOrgLevelResourceResolver(AggregationStrategy aggregationStrategy, + String organizationId) + throws OrgResourceHierarchyTraverseException { + + return orgResourceResolverService.getResourcesFromOrgHierarchy( + organizationId, + orgId -> { + MockResource resource = mockResourceManagementService.getOrgResource(orgId); + return Optional.ofNullable(resource); + }, + aggregationStrategy); + } + + /** + * Invoke the application level resource resolver. + * + * @param aggregationStrategy Aggregation strategy. + * @param organizationId Organization ID. + * @param applicationId Application ID. + * @return Resolved resource. + * @throws OrgResourceHierarchyTraverseException If an error occurs while resolving the resource. + */ + private MockResource invokeAppLevelResourceResolver(AggregationStrategy aggregationStrategy, + String organizationId, String applicationId) + throws OrgResourceHierarchyTraverseException { + + return orgResourceResolverService.getResourcesFromOrgHierarchy( + organizationId, + applicationId, + (orgId, appId) -> { + MockResource resource = mockResourceManagementService.getAppResource(orgId, appId); + return Optional.ofNullable(resource); + }, + aggregationStrategy); + } + + /** + * Assert the resolved resource with the actual resource. + * + * @param resolvedResource Resolved resource. + * @param actualResource Actual resource. + */ + private void assertResolvedResponse(MockResource resolvedResource, MockResource actualResource) { + + assertNotNull(resolvedResource); + assertEquals(resolvedResource.getId(), actualResource.getId()); + assertEquals(resolvedResource.getResourceName(), actualResource.getResourceName()); + assertEquals(resolvedResource.getOrgId(), actualResource.getOrgId()); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java new file mode 100644 index 000000000..814b010f5 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl; + +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model.MockResource; + +import java.util.HashMap; +import java.util.Map; + +/** + * Service for managing mocked resources at both organization and application levels. + *

+ * This class maintains separate mappings for organization-level and application-level + * resources, providing methods to add and retrieve resources. These mappings simulate a database, + * offering a simple in-memory representation for resource storage and retrieval operations. + *

+ * This implementation is intended for testing or mocking purposes and simulates a resource management system. + */ +public class MockResourceManagementService { + + private final Map orgResources; + private final Map appResources; + + /** + * Initializes the mock resource management service with empty resource mappings + * for both organization-level and application-level resources. + */ + public MockResourceManagementService() { + + this.orgResources = new HashMap<>(); + this.appResources = new HashMap<>(); + } + + /** + * Adds an organization-level resource to the management service. + * + * @param orgResource The resource to be added. The resource's organization ID + * ({@link MockResource#getOrgId}) is used as the key for storage. + */ + public void addOrgResource(MockResource orgResource) { + + orgResources.put(orgResource.getOrgId(), orgResource); + } + + /** + * Retrieves an organization-level resource by its organization ID. + * + * @param orgId The unique identifier of the organization. + * @return The resource associated with the given organization ID, or {@code null} + * if no resource exists for the provided ID. + */ + public MockResource getOrgResource(String orgId) { + + return orgResources.get(orgId); + } + + /** + * Adds an application-level resource to the management service. + * + * @param appResource The resource to be added. The key for storage is derived + * by concatenating the resource's organization ID and + * application ID ({@link MockResource#getAppId}), separated by a colon. + */ + public void addAppResource(MockResource appResource) { + + appResources.put(appResource.getOrgId() + ":" + appResource.getAppId(), appResource); + } + + /** + * Retrieves an application-level resource by organization ID and application ID. + *

+ * If no application-level resource is found for the given organization and application IDs, + * the method falls back to returning the corresponding organization-level resource, if available. + * + * @param orgId The unique identifier of the organization. + * @param appId The unique identifier of the application within the organization. + * @return The application-level resource if found; otherwise, the organization-level + * resource associated with the organization ID. Returns {@code null} if no matching + * resource is available at either level. + */ + public MockResource getAppResource(String orgId, String appId) { + + MockResource appResource = appResources.get(orgId + ":" + appId); + if (appResource == null) { + return orgResources.get(orgId); + } + return appResource; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java new file mode 100644 index 000000000..9b4cbb86e --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model; + +/** + * Represents a mocked resource in an organization or application hierarchy. + *

+ * This class models resources that are associated either at the organization level + * or at the application level within an organization. It includes identifiers and + * metadata such as resource ID, resource name, organization ID, and optionally, + * an application ID for application-specific resources. + */ +public class MockResource { + + private int id; + private String resourceName; + private String orgId; + private String appId; + + /** + * Constructs a mocked resource associated with an organization. + * + * @param id Unique identifier of the resource. + * @param resourceName Descriptive name of the resource. + * @param orgId Identifier of the organization the resource belongs to. + */ + public MockResource(int id, String resourceName, String orgId) { + + this.id = id; + this.resourceName = resourceName; + this.orgId = orgId; + } + + /** + * Constructs a mocked resource associated with a specific application within an organization. + * + * @param id Unique identifier of the resource. + * @param resourceName Descriptive name of the resource. + * @param orgId Identifier of the organization the resource belongs to. + * @param appId Identifier of the application the resource is associated with. + */ + public MockResource(int id, String resourceName, String orgId, String appId) { + + this(id, resourceName, orgId); + this.appId = appId; + } + + /** + * Retrieves the unique identifier of the resource. + * + * @return The resource ID. + */ + public int getId() { + + return id; + } + + /** + * Sets the unique identifier of the resource. + * + * @param id The resource ID to set. + */ + public void setId(int id) { + + this.id = id; + } + + /** + * Retrieves the name of the resource. + * + * @return The resource name. + */ + public String getResourceName() { + + return resourceName; + } + + /** + * Sets the name of the resource. + * + * @param resourceName The name to assign to the resource. + */ + public void setResourceName(String resourceName) { + + this.resourceName = resourceName; + } + + /** + * Retrieves the organization ID associated with the resource. + * + * @return The organization ID. + */ + public String getOrgId() { + + return orgId; + } + + /** + * Sets the organization ID associated with the resource. + * + * @param orgId The organization ID to set. + */ + public void setOrgId(String orgId) { + + this.orgId = orgId; + } + + /** + * Retrieves the application ID associated with the resource. + * + * @return The application ID, or {@code null} if the resource is not associated with a specific application. + */ + public String getAppId() { + + return appId; + } + + /** + * Sets the application ID associated with the resource. + * + * @param appId The application ID to set. + */ + public void setAppId(String appId) { + + this.appId = appId; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml new file mode 100644 index 000000000..539837328 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/features/org.wso2.carbon.identity.organization.management.server.feature/pom.xml b/features/org.wso2.carbon.identity.organization.management.server.feature/pom.xml index ea5333ebc..49690c6e0 100644 --- a/features/org.wso2.carbon.identity.organization.management.server.feature/pom.xml +++ b/features/org.wso2.carbon.identity.organization.management.server.feature/pom.xml @@ -83,6 +83,10 @@ org.wso2.carbon.identity.organization.management org.wso2.carbon.identity.organization.discovery.service + + org.wso2.carbon.identity.organization.management + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service + @@ -120,6 +124,7 @@ org.wso2.carbon.identity.organization.management:org.wso2.carbon.identity.organization.user.invitation.management org.wso2.carbon.identity.organization.management:org.wso2.carbon.identity.organization.config.service org.wso2.carbon.identity.organization.management:org.wso2.carbon.identity.organization.discovery.service + org.wso2.carbon.identity.organization.management:org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service org.wso2.carbon.core:compatible:${carbon.kernel.feature.version} diff --git a/pom.xml b/pom.xml index f8b771c35..dbe7c8e9d 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ components/org.wso2.carbon.identity.organization.config.service components/org.wso2.carbon.identity.organization.discovery.service components/org.wso2.carbon.identity.organization.management.organization.user.sharing + components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service @@ -186,6 +187,12 @@ ${project.version} provided + + org.wso2.carbon.identity.organization.management + org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service + ${project.version} + provided + org.wso2.carbon.identity.framework