getResources() {
+
+ return resources;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java
new file mode 100644
index 000000000000..5137c660660f
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.model;
+
+/**
+ * Lightweight summary of a Policy for list views.
+ * Carries only the fields a list page needs (id and name); the full {@link Policy} with hydrated
+ * resources is retrieved via {@code getPolicyById}.
+ */
+public class PolicyBasicInfo {
+
+ private final String id;
+ private final String name;
+
+ public PolicyBasicInfo(String id, String name) {
+
+ this.id = id;
+ this.name = name;
+ }
+
+ /**
+ * Returns the policy ID.
+ *
+ * @return Policy ID.
+ */
+ public String getId() {
+
+ return id;
+ }
+
+ /**
+ * Returns the policy name.
+ *
+ * @return Policy name.
+ */
+ public String getName() {
+
+ return name;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java
new file mode 100644
index 000000000000..4f685023e047
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.model;
+
+import org.wso2.carbon.identity.rule.management.api.model.Rule;
+
+/**
+ * Association between a Policy and a resource (a Rule or an Action) for a specific target.
+ * The target is a selector value (e.g. a device platform); one target may have at most one
+ * resource of each {@link ResourceType} per policy.
+ */
+public class PolicyResource {
+
+ private final String id;
+ private final String target;
+ private final ResourceType resourceType;
+ private final String resourceId;
+ private final Rule rule;
+
+ public PolicyResource(String id, String target, ResourceType resourceType, String resourceId, Rule rule) {
+
+ this.id = id;
+ this.target = target;
+ this.resourceType = resourceType;
+ this.resourceId = resourceId;
+ this.rule = rule;
+ }
+
+ public String getId() {
+
+ return id;
+ }
+
+ public String getTarget() {
+
+ return target;
+ }
+
+ public ResourceType getResourceType() {
+
+ return resourceType;
+ }
+
+ public String getResourceId() {
+
+ return resourceId;
+ }
+
+ /**
+ * Returns the hydrated rule. Non-null only when {@link #getResourceType()} is {@link ResourceType#RULE}
+ * and the resource has been hydrated by the service layer.
+ *
+ * @return Hydrated rule, or {@code null}.
+ */
+ public Rule getRule() {
+
+ return rule;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java
new file mode 100644
index 000000000000..9bff43016983
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.model;
+
+/**
+ * Type of resource attached to a policy for a given target.
+ * Stored in the database as its {@code name()} value.
+ */
+public enum ResourceType {
+
+ /** An IS-native rule managed by rule-mgt. */
+ RULE,
+
+ /** An external action managed by action-mgt. */
+ ACTION
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java
new file mode 100644
index 000000000000..0b8a865bce87
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.service;
+
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException;
+import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext;
+import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult;
+
+/**
+ * Service interface for evaluating compliance policies against a caller-supplied rule context.
+ */
+public interface PolicyEvaluationService {
+
+ /**
+ * Evaluates the rule matching {@code ruleSelector} within the named policy against the provided
+ * {@link FlowContext}.
+ *
+ * Returns {@code null} when no policy with {@code policyName} exists — the caller interprets
+ * this as "no constraint" and decides the appropriate response. Returns a satisfied result with
+ * a {@code null} rule ID when a policy exists but no rule matches the selector (compliant by default).
+ *
+ * @param policyName Name of the policy to evaluate.
+ * @param ruleSelector Value used to select a rule within the policy (e.g. device platform).
+ * @param flowContext Context carrying the data fields for rule evaluation.
+ * @param tenantDomain Tenant domain.
+ * @return Evaluation result, or {@code null} if the named policy does not exist.
+ * @throws PolicyManagementException If policy retrieval fails.
+ * @throws RuleEvaluationException If rule evaluation fails.
+ */
+ RuleEvaluationResult evaluate(String policyName, String ruleSelector,
+ FlowContext flowContext, String tenantDomain)
+ throws PolicyManagementException, RuleEvaluationException;
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java
new file mode 100644
index 000000000000..8b4d37a54bdd
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.service;
+
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+
+import java.util.List;
+
+/**
+ * Service interface for managing device compliance policies.
+ */
+public interface PolicyManagementService {
+
+ /**
+ * Creates a new policy. The supplied ID is ignored; a server-generated UUID is assigned.
+ *
+ * @param policy Policy to create. Name must be non-blank.
+ * @param tenantDomain Tenant domain.
+ * @return Created policy with the server-assigned ID.
+ * @throws PolicyManagementException If the policy name is blank or persistence fails.
+ */
+ Policy addPolicy(Policy policy, String tenantDomain) throws PolicyManagementException;
+
+ /**
+ * Replaces an existing policy (PUT semantics). All fields, rules, and actions are replaced wholesale.
+ *
+ * @param policy Policy with the new state. ID must reference an existing policy.
+ * @param tenantDomain Tenant domain.
+ * @return Updated policy as persisted.
+ * @throws PolicyManagementException If the policy is not found or persistence fails.
+ */
+ Policy updatePolicy(Policy policy, String tenantDomain) throws PolicyManagementException;
+
+ /**
+ * Deletes the policy. Idempotent — deleting a non-existent policy is a no-op.
+ *
+ * @param policyId Policy ID.
+ * @param tenantDomain Tenant domain.
+ * @throws PolicyManagementException If persistence fails.
+ */
+ void deletePolicy(String policyId, String tenantDomain) throws PolicyManagementException;
+
+ /**
+ * Returns the policy by ID with each rule-typed {@link PolicyResource} hydrated from rule-mgt.
+ *
+ * @param policyId Policy ID.
+ * @param tenantDomain Tenant domain.
+ * @return Policy with hydrated rules, or {@code null} if not found.
+ * @throws PolicyManagementException If retrieval or rule hydration fails.
+ */
+ Policy getPolicyById(String policyId, String tenantDomain) throws PolicyManagementException;
+
+ /**
+ * Returns the policy by name with each rule-typed {@link PolicyResource} hydrated from rule-mgt.
+ *
+ * @param policyName Policy name.
+ * @param tenantDomain Tenant domain.
+ * @return Policy with hydrated rules, or {@code null} if not found.
+ * @throws PolicyManagementException If retrieval or rule hydration fails.
+ */
+ Policy getPolicyByName(String policyName, String tenantDomain) throws PolicyManagementException;
+
+ /**
+ * Returns a page of policy summaries for the tenant, optionally filtered by name. Summaries are
+ * lightweight (no hydrated rules); call {@link #getPolicyById} for the full hydrated policy.
+ *
+ * @param tenantDomain Tenant domain.
+ * @param filter Name filter; {@code null} or blank returns all policies.
+ * @param offset Zero-based start index.
+ * @param limit Maximum number of results to return.
+ * @return Page of policy summaries (never {@code null}).
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ List getPolicies(String tenantDomain, String filter, int offset, int limit)
+ throws PolicyManagementException;
+
+ /**
+ * Returns the total number of policies matching the given filter, for pagination.
+ *
+ * @param tenantDomain Tenant domain.
+ * @param filter Name filter; {@code null} or blank counts all policies.
+ * @return Total number of matching policies.
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ int getPolicyCount(String tenantDomain, String filter) throws PolicyManagementException;
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java
new file mode 100644
index 000000000000..ad56e1b2adce
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.api.util;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementServerException;
+
+/**
+ * Utility class for constructing policy management exceptions with the standard
+ * (message, description, code[, cause]) shape backed by ErrorMessage entries.
+ */
+public class PolicyManagementExceptionHandler {
+
+ private PolicyManagementExceptionHandler() {
+
+ }
+
+ public static PolicyManagementClientException handleClientException(ErrorMessage error, String... data) {
+
+ String description = error.getDescription();
+ if (ArrayUtils.isNotEmpty(data)) {
+ description = String.format(description, (Object[]) data);
+ }
+
+ return new PolicyManagementClientException(error.getMessage(), description, error.getCode());
+ }
+
+ public static PolicyManagementClientException handleClientException(ErrorMessage error, Throwable e,
+ String... data) {
+
+ String description = error.getDescription();
+ if (ArrayUtils.isNotEmpty(data)) {
+ description = String.format(description, (Object[]) data);
+ }
+
+ return new PolicyManagementClientException(error.getMessage(), description, error.getCode(), e);
+ }
+
+ public static PolicyManagementServerException handleServerException(ErrorMessage error, Throwable e,
+ String... data) {
+
+ String description = error.getDescription();
+ if (ArrayUtils.isNotEmpty(data)) {
+ description = String.format(description, (Object[]) data);
+ }
+
+ return new PolicyManagementServerException(error.getMessage(), description, error.getCode(), e);
+ }
+
+ public static PolicyManagementServerException handleServerException(ErrorMessage error, String... data) {
+
+ String description = error.getDescription();
+ if (ArrayUtils.isNotEmpty(data)) {
+ description = String.format(description, (Object[]) data);
+ }
+
+ return new PolicyManagementServerException(error.getMessage(), description, error.getCode());
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java
new file mode 100644
index 000000000000..30bce577a27d
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.cache;
+
+import org.wso2.carbon.identity.core.cache.BaseCache;
+import org.wso2.carbon.utils.CarbonUtils;
+
+/**
+ * Cache for Policy Management.
+ */
+public class PolicyCache extends BaseCache {
+
+ private static final String CACHE_NAME = "PolicyCache";
+ private static final PolicyCache INSTANCE = new PolicyCache();
+
+ private PolicyCache() {
+
+ super(CACHE_NAME);
+ }
+
+ public static PolicyCache getInstance() {
+
+ CarbonUtils.checkSecurity();
+ return INSTANCE;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java
new file mode 100644
index 000000000000..dcfd20a2a3fe
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.cache;
+
+import org.wso2.carbon.identity.core.cache.CacheEntry;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+
+/**
+ * Cache entry for Policy Management.
+ * Stores an un-hydrated Policy (rule IDs only, no full Rule objects).
+ */
+public class PolicyCacheEntry extends CacheEntry {
+
+ private static final long serialVersionUID = -4310859152763490172L;
+
+ private final Policy policy;
+
+ public PolicyCacheEntry(Policy policy) {
+
+ this.policy = policy;
+ }
+
+ public Policy getPolicy() {
+
+ return policy;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java
new file mode 100644
index 000000000000..0b64537b30b7
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.cache;
+
+import org.wso2.carbon.identity.core.cache.CacheKey;
+
+/**
+ * Cache key for Policy Management.
+ * Identifies a cached policy by its name; tenant scoping is handled by the cache's tenantId parameter.
+ */
+public class PolicyCacheKey extends CacheKey {
+
+ private static final long serialVersionUID = 1861274580132498765L;
+
+ private final String policyName;
+
+ public PolicyCacheKey(String policyName) {
+
+ this.policyName = policyName;
+ }
+
+ public String getPolicyName() {
+
+ return policyName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (!(o instanceof PolicyCacheKey)) {
+ return false;
+ }
+ return policyName.equals(((PolicyCacheKey) o).getPolicyName());
+ }
+
+ @Override
+ public int hashCode() {
+
+ return policyName.hashCode();
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java
new file mode 100644
index 000000000000..3bd0cac0a859
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.component;
+
+import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService;
+import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;
+import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService;
+
+/**
+ * Service holder for the policy management component.
+ * Provides access to OSGi services consumed by this bundle.
+ */
+public class PolicyMgtComponentServiceHolder {
+
+ private static final PolicyMgtComponentServiceHolder INSTANCE = new PolicyMgtComponentServiceHolder();
+
+ private RuleManagementService ruleManagementService;
+ private RuleEvaluationService ruleEvaluationService;
+ private PolicyManagementService policyManagementService;
+
+ private PolicyMgtComponentServiceHolder() {
+
+ }
+
+ /**
+ * Returns the singleton service holder instance.
+ *
+ * @return Service holder.
+ */
+ public static PolicyMgtComponentServiceHolder getInstance() {
+
+ return INSTANCE;
+ }
+
+ public RuleManagementService getRuleManagementService() {
+
+ return ruleManagementService;
+ }
+
+ public void setRuleManagementService(RuleManagementService ruleManagementService) {
+
+ this.ruleManagementService = ruleManagementService;
+ }
+
+ public RuleEvaluationService getRuleEvaluationService() {
+
+ return ruleEvaluationService;
+ }
+
+ public void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {
+
+ this.ruleEvaluationService = ruleEvaluationService;
+ }
+
+ public PolicyManagementService getPolicyManagementService() {
+
+ return policyManagementService;
+ }
+
+ public void setPolicyManagementService(PolicyManagementService policyManagementService) {
+
+ this.policyManagementService = policyManagementService;
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java
new file mode 100644
index 000000000000..65259e4b8066
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.component;
+
+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.policy.management.api.service.PolicyEvaluationService;
+import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService;
+import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyEvaluationServiceImpl;
+import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyManagementServiceImpl;
+import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;
+import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService;
+
+/**
+ * OSGi DS component that registers the PolicyManagementService.
+ */
+@Component(
+ name = "policy.management.service.component",
+ immediate = true
+)
+public class PolicyMgtServiceComponent {
+
+ private static final Log LOG = LogFactory.getLog(PolicyMgtServiceComponent.class);
+
+ @Activate
+ protected void activate(ComponentContext context) {
+
+ try {
+ BundleContext bundleCtx = context.getBundleContext();
+ PolicyManagementServiceImpl policyManagementService = new PolicyManagementServiceImpl();
+ bundleCtx.registerService(PolicyManagementService.class.getName(), policyManagementService, null);
+ PolicyMgtComponentServiceHolder.getInstance().setPolicyManagementService(policyManagementService);
+
+ PolicyEvaluationServiceImpl policyEvaluationService = new PolicyEvaluationServiceImpl();
+ bundleCtx.registerService(PolicyEvaluationService.class.getName(), policyEvaluationService, null);
+ LOG.debug("Policy management bundle activated.");
+ } catch (Throwable e) {
+ LOG.error("Error while initializing policy management service component.", e);
+ }
+ }
+
+ @Deactivate
+ protected void deactivate(ComponentContext context) {
+
+ LOG.debug("Policy management bundle deactivated.");
+ }
+
+ @Reference(
+ name = "rule.management.service",
+ service = RuleManagementService.class,
+ cardinality = ReferenceCardinality.MANDATORY,
+ policy = ReferencePolicy.DYNAMIC,
+ unbind = "unsetRuleManagementService"
+ )
+ protected void setRuleManagementService(RuleManagementService ruleManagementService) {
+
+ PolicyMgtComponentServiceHolder.getInstance().setRuleManagementService(ruleManagementService);
+ LOG.debug("RuleManagementService set in Policy Management component.");
+ }
+
+ protected void unsetRuleManagementService(RuleManagementService ruleManagementService) {
+
+ PolicyMgtComponentServiceHolder holder = PolicyMgtComponentServiceHolder.getInstance();
+ if (holder.getRuleManagementService() == ruleManagementService) {
+ holder.setRuleManagementService(null);
+ }
+ LOG.debug("RuleManagementService unset in Policy Management component.");
+ }
+
+ @Reference(
+ name = "rule.evaluation.service",
+ service = RuleEvaluationService.class,
+ cardinality = ReferenceCardinality.MANDATORY,
+ policy = ReferencePolicy.DYNAMIC,
+ unbind = "unsetRuleEvaluationService"
+ )
+ protected void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {
+
+ PolicyMgtComponentServiceHolder.getInstance().setRuleEvaluationService(ruleEvaluationService);
+ LOG.debug("RuleEvaluationService set in Policy Management component.");
+ }
+
+ protected void unsetRuleEvaluationService(RuleEvaluationService ruleEvaluationService) {
+
+ PolicyMgtComponentServiceHolder holder = PolicyMgtComponentServiceHolder.getInstance();
+ if (holder.getRuleEvaluationService() == ruleEvaluationService) {
+ holder.setRuleEvaluationService(null);
+ }
+ LOG.debug("RuleEvaluationService unset in Policy Management component.");
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java
new file mode 100644
index 000000000000..ce2ffb966695
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.constant;
+
+/**
+ * SQL constants for Policy Management DAO.
+ * Two tables: IDN_POLICY (main) and IDN_POLICY_RESOURCE (polymorphic attachment of rules/actions to a policy).
+ */
+public final class PolicyMgtSQLConstants {
+
+ private PolicyMgtSQLConstants() {
+
+ }
+
+ /** Column name constants. */
+ public static final class Column {
+
+ public static final String ID = "ID";
+ public static final String POLICY_ID = "POLICY_ID";
+ public static final String POLICY_NAME = "POLICY_NAME";
+ public static final String TENANT_ID = "TENANT_ID";
+ public static final String TARGET = "TARGET";
+ public static final String RESOURCE_TYPE = "RESOURCE_TYPE";
+ public static final String RESOURCE_ID = "RESOURCE_ID";
+
+ public static final String FILTER = "FILTER";
+ public static final String LIMIT = "LIMIT";
+ public static final String OFFSET = "OFFSET";
+ public static final String LOWER_BOUND = "LOWER_BOUND";
+ public static final String UPPER_BOUND = "UPPER_BOUND";
+
+ private Column() {
+
+ }
+ }
+
+ /** SQL query constants. */
+ public static final class Query {
+
+ // IDN_POLICY table.
+ public static final String ADD_POLICY =
+ "INSERT INTO IDN_POLICY (ID, POLICY_NAME, TENANT_ID) " +
+ "VALUES (:ID;, :POLICY_NAME;, :TENANT_ID;)";
+
+ public static final String UPDATE_POLICY =
+ "UPDATE IDN_POLICY SET POLICY_NAME = :POLICY_NAME; " +
+ "WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;";
+
+ public static final String DELETE_POLICY =
+ "DELETE FROM IDN_POLICY WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;";
+
+ public static final String GET_POLICY_BY_ID =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY " +
+ "WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;";
+
+ public static final String GET_POLICY_BY_NAME =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY " +
+ "WHERE POLICY_NAME = :POLICY_NAME; AND TENANT_ID = :TENANT_ID;";
+
+ // Paginated policy listing. A name filter fragment (LOWER(POLICY_NAME) LIKE LOWER(:FILTER;))
+ // is appended via the *_FILTER variants. Pagination syntax differs per database, so a variant
+ // is selected at runtime based on the detected database type.
+
+ // Default: H2, MySQL, MariaDB, PostgreSQL.
+ public static final String GET_POLICIES_PAGINATED =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "ORDER BY POLICY_NAME ASC LIMIT :LIMIT; OFFSET :OFFSET;";
+
+ public static final String GET_POLICIES_PAGINATED_FILTER =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) " +
+ "ORDER BY POLICY_NAME ASC LIMIT :LIMIT; OFFSET :OFFSET;";
+
+ // MS SQL Server.
+ public static final String GET_POLICIES_PAGINATED_MSSQL =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "ORDER BY POLICY_NAME ASC OFFSET :OFFSET; ROWS FETCH NEXT :LIMIT; ROWS ONLY";
+
+ public static final String GET_POLICIES_PAGINATED_FILTER_MSSQL =
+ "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) " +
+ "ORDER BY POLICY_NAME ASC OFFSET :OFFSET; ROWS FETCH NEXT :LIMIT; ROWS ONLY";
+
+ // Oracle.
+ public static final String GET_POLICIES_PAGINATED_ORACLE =
+ "SELECT ID, POLICY_NAME FROM (SELECT ID, POLICY_NAME, rownum AS rnum FROM " +
+ "(SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "ORDER BY POLICY_NAME ASC) WHERE rownum <= :UPPER_BOUND;) WHERE rnum > :OFFSET;";
+
+ public static final String GET_POLICIES_PAGINATED_FILTER_ORACLE =
+ "SELECT ID, POLICY_NAME FROM (SELECT ID, POLICY_NAME, rownum AS rnum FROM " +
+ "(SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) ORDER BY POLICY_NAME ASC) " +
+ "WHERE rownum <= :UPPER_BOUND;) WHERE rnum > :OFFSET;";
+
+ // DB2.
+ public static final String GET_POLICIES_PAGINATED_DB2 =
+ "SELECT ID, POLICY_NAME FROM (SELECT ROW_NUMBER() OVER(ORDER BY POLICY_NAME ASC) AS rn, " +
+ "ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID;) " +
+ "WHERE rn BETWEEN :LOWER_BOUND; AND :UPPER_BOUND;";
+
+ public static final String GET_POLICIES_PAGINATED_FILTER_DB2 =
+ "SELECT ID, POLICY_NAME FROM (SELECT ROW_NUMBER() OVER(ORDER BY POLICY_NAME ASC) AS rn, " +
+ "ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;)) WHERE rn BETWEEN :LOWER_BOUND; AND :UPPER_BOUND;";
+
+ public static final String GET_POLICIES_COUNT =
+ "SELECT COUNT(*) FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID;";
+
+ public static final String GET_POLICIES_COUNT_FILTER =
+ "SELECT COUNT(*) FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " +
+ "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;)";
+
+ public static final String CHECK_POLICY_NAME_EXISTS =
+ "SELECT ID FROM IDN_POLICY " +
+ "WHERE POLICY_NAME = :POLICY_NAME; AND TENANT_ID = :TENANT_ID;";
+
+ // IDN_POLICY_RESOURCE attachment table.
+ public static final String ADD_POLICY_RESOURCE =
+ "INSERT INTO IDN_POLICY_RESOURCE (ID, POLICY_ID, TARGET, RESOURCE_TYPE, RESOURCE_ID) " +
+ "VALUES (:ID;, :POLICY_ID;, :TARGET;, :RESOURCE_TYPE;, :RESOURCE_ID;)";
+
+ public static final String GET_POLICY_RESOURCES =
+ "SELECT ID, TARGET, RESOURCE_TYPE, RESOURCE_ID FROM IDN_POLICY_RESOURCE " +
+ "WHERE POLICY_ID = :POLICY_ID;";
+
+ public static final String DELETE_POLICY_RESOURCES =
+ "DELETE FROM IDN_POLICY_RESOURCE WHERE POLICY_ID = :POLICY_ID;";
+
+ private Query() {
+
+ }
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java
new file mode 100644
index 000000000000..135604bd183d
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.dao;
+
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+
+import java.util.List;
+
+/**
+ * Interface for Policy Management DAO.
+ * Handles CRUD for IDN_POLICY and IDN_POLICY_RESOURCE tables.
+ * Returns Policy objects with PolicyResource lists populated (resourceIds only — rules are hydrated by the
+ * service layer).
+ */
+public interface PolicyManagementDAO {
+
+ /**
+ * Persists a new policy and its resources.
+ *
+ * @param policy Policy to persist (id pre-assigned).
+ * @param tenantId Tenant ID.
+ * @return Persisted policy.
+ * @throws PolicyManagementException If persistence fails.
+ */
+ Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException;
+
+ /**
+ * Replaces an existing policy and its resources (the resource rows are deleted and re-inserted).
+ *
+ * @param policy Policy with the new state.
+ * @param tenantId Tenant ID.
+ * @return Updated policy.
+ * @throws PolicyManagementException If persistence fails.
+ */
+ Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException;
+
+ /**
+ * Deletes the policy and its resources (cascaded). Idempotent.
+ *
+ * @param policyId Policy ID.
+ * @param tenantId Tenant ID.
+ * @throws PolicyManagementException If persistence fails.
+ */
+ void deletePolicy(String policyId, int tenantId) throws PolicyManagementException;
+
+ /**
+ * Returns the policy by ID with its resources (resource ids only; rules are not hydrated).
+ *
+ * @param policyId Policy ID.
+ * @param tenantId Tenant ID.
+ * @return Policy, or {@code null} if no policy exists for the given ID and tenant.
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException;
+
+ /**
+ * Returns the policy by name with its resources (resource ids only; rules are not hydrated).
+ *
+ * @param policyName Policy name.
+ * @param tenantId Tenant ID.
+ * @return Policy, or {@code null} if no policy exists for the given name and tenant.
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException;
+
+ /**
+ * Returns a paginated, optionally name-filtered list of policies (basic info only), ordered by name.
+ *
+ * @param tenantId Tenant ID.
+ * @param filter Case-insensitive substring to match against the policy name; {@code null}/blank for no filter.
+ * @param offset Number of records to skip.
+ * @param limit Maximum number of records to return; a non-positive value yields an empty list.
+ * @return List of matching policies (never {@code null}).
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ List getPolicies(int tenantId, String filter, int offset, int limit)
+ throws PolicyManagementException;
+
+ /**
+ * Returns the total number of policies for the tenant, applying the same name filter as
+ * {@link #getPolicies(int, String, int, int)}.
+ *
+ * @param tenantId Tenant ID.
+ * @param filter Case-insensitive substring to match against the policy name; {@code null}/blank for no filter.
+ * @return Matching policy count.
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ int getPolicyCount(int tenantId, String filter) throws PolicyManagementException;
+
+ /**
+ * Returns the ID of the policy with the given name, used for name-uniqueness checks.
+ *
+ * @param policyName Policy name.
+ * @param tenantId Tenant ID.
+ * @return Policy ID, or {@code null} if no policy exists with that name for the tenant.
+ * @throws PolicyManagementException If retrieval fails.
+ */
+ String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException;
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java
new file mode 100644
index 000000000000..0d786273e6b7
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.dao.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCache;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheEntry;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheKey;
+import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO;
+
+import java.util.List;
+
+/**
+ * Cache-backed Policy Management DAO.
+ * Wraps a PolicyManagementDAO with an in-process cache to reduce DB hits on the hot path.
+ * Hot path: getPolicyByName (called on every device evaluation) is served from cache after first read.
+ * Cache is keyed by policy name only and is precisely invalidated on update and delete.
+ */
+public class CacheBackedPolicyManagementDAO implements PolicyManagementDAO {
+
+ private static final Log LOG = LogFactory.getLog(CacheBackedPolicyManagementDAO.class);
+
+ private final PolicyManagementDAO policyManagementDAO;
+ private final PolicyCache policyCache;
+
+ public CacheBackedPolicyManagementDAO(PolicyManagementDAO policyManagementDAO) {
+
+ this.policyManagementDAO = policyManagementDAO;
+ policyCache = PolicyCache.getInstance();
+ }
+
+ /**
+ * Add a new Policy.
+ * This method directly invokes the data layer operation without caching the result.
+ *
+ * @param policy Policy object.
+ * @param tenantId Tenant ID.
+ * @return Created Policy object.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException {
+
+ return policyManagementDAO.addPolicy(policy, tenantId);
+ }
+
+ /**
+ * Update an existing Policy.
+ * Reads the persisted policy first to detect renames, then delegates the update,
+ * and finally clears the name-based cache entries for both the old and new names.
+ *
+ * @param policy Policy object with updated state.
+ * @param tenantId Tenant ID.
+ * @return Updated Policy object.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException {
+
+ Policy existingPolicy = policyManagementDAO.getPolicyById(policy.getId(), tenantId);
+ Policy updatedPolicy = policyManagementDAO.updatePolicy(policy, tenantId);
+
+ if (existingPolicy != null) {
+ policyCache.clearCacheEntry(new PolicyCacheKey(existingPolicy.getName()), tenantId);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy cache cleared for old name: " + existingPolicy.getName() + " on update.");
+ }
+ }
+ policyCache.clearCacheEntry(new PolicyCacheKey(policy.getName()), tenantId);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy cache cleared for name: " + policy.getName() + " on update.");
+ }
+ return updatedPolicy;
+ }
+
+ /**
+ * Delete a Policy.
+ * Reads the persisted policy first to resolve its name, delegates the delete,
+ * then clears the name-based cache entry.
+ *
+ * @param policyId Policy ID.
+ * @param tenantId Tenant ID.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public void deletePolicy(String policyId, int tenantId) throws PolicyManagementException {
+
+ Policy existingPolicy = policyManagementDAO.getPolicyById(policyId, tenantId);
+ policyManagementDAO.deletePolicy(policyId, tenantId);
+ if (existingPolicy != null) {
+ policyCache.clearCacheEntry(new PolicyCacheKey(existingPolicy.getName()), tenantId);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy cache cleared for name: " + existingPolicy.getName() + " on delete.");
+ }
+ }
+ }
+
+ /**
+ * Get a Policy by Policy ID.
+ * Not cached; always delegates to the underlying DAO.
+ *
+ * @param policyId Policy ID.
+ * @param tenantId Tenant ID.
+ * @return Policy object, or {@code null} if not found.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException {
+
+ return policyManagementDAO.getPolicyById(policyId, tenantId);
+ }
+
+ /**
+ * Get a Policy by Policy name.
+ * This method first checks the cache for the Policy object.
+ * If the Policy object is not found in the cache, it invokes the data layer operation
+ * and caches the result under the name-based key.
+ *
+ * @param policyName Policy name.
+ * @param tenantId Tenant ID.
+ * @return Policy object, or {@code null} if not found.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException {
+
+ PolicyCacheEntry cacheEntry = policyCache.getValueFromCache(
+ new PolicyCacheKey(policyName), tenantId);
+ if (cacheEntry != null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy cache hit for name: " + policyName);
+ }
+ return cacheEntry.getPolicy();
+ }
+ Policy policy = policyManagementDAO.getPolicyByName(policyName, tenantId);
+ if (policy != null) {
+ policyCache.addToCacheOnRead(
+ new PolicyCacheKey(policyName), new PolicyCacheEntry(policy), tenantId);
+ }
+ return policy;
+ }
+
+ /**
+ * Get a page of Policy summaries for a tenant, optionally filtered by name.
+ * Paginated lists are not cached; this directly invokes the data layer operation.
+ *
+ * @param tenantId Tenant ID.
+ * @param filter Name filter; {@code null} or blank means no filter.
+ * @param offset Zero-based start index.
+ * @param limit Maximum number of results.
+ * @return List of policy summaries. Never {@code null}.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public List getPolicies(int tenantId, String filter, int offset, int limit)
+ throws PolicyManagementException {
+
+ return policyManagementDAO.getPolicies(tenantId, filter, offset, limit);
+ }
+
+ /**
+ * Count Policies matching the given filter. Not cached; directly invokes the data layer.
+ *
+ * @param tenantId Tenant ID.
+ * @param filter Name filter; {@code null} or blank means count all.
+ * @return Total number of matching policies.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public int getPolicyCount(int tenantId, String filter) throws PolicyManagementException {
+
+ return policyManagementDAO.getPolicyCount(tenantId, filter);
+ }
+
+ /**
+ * Get a Policy ID by Policy name.
+ * This method directly invokes the data layer operation without caching.
+ *
+ * @param policyName Policy name.
+ * @param tenantId Tenant ID.
+ * @return Policy ID, or {@code null} if not found.
+ * @throws PolicyManagementException Policy Management Exception.
+ */
+ @Override
+ public String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException {
+
+ return policyManagementDAO.getPolicyIdByName(policyName, tenantId);
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java
new file mode 100644
index 000000000000..320c1dfbe0d1
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.dao.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wso2.carbon.database.utils.jdbc.NamedJdbcTemplate;
+import org.wso2.carbon.database.utils.jdbc.NamedPreparedStatement;
+import org.wso2.carbon.database.utils.jdbc.NamedTemplate;
+import org.wso2.carbon.database.utils.jdbc.exceptions.DataAccessException;
+import org.wso2.carbon.database.utils.jdbc.exceptions.TransactionException;
+import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil;
+import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
+import org.wso2.carbon.identity.core.util.JdbcUtils;
+import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.api.util.PolicyManagementExceptionHandler;
+import org.wso2.carbon.identity.policy.management.internal.constant.PolicyMgtSQLConstants;
+import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO;
+
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Policy Management DAO Implementation.
+ */
+public class PolicyManagementDAOImpl implements PolicyManagementDAO {
+
+ private static final Log LOG = LogFactory.getLog(PolicyManagementDAOImpl.class);
+
+ @Override
+ public Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException {
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ jdbcTemplate.withTransaction(template -> {
+ template.executeInsert(
+ PolicyMgtSQLConstants.Query.ADD_POLICY,
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, policy.getId());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policy.getName());
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ },
+ policy,
+ false);
+
+ for (PolicyResource policyResource : policy.getResources()) {
+ final String resourceRowId = UUID.randomUUID().toString();
+ template.executeInsert(
+ PolicyMgtSQLConstants.Query.ADD_POLICY_RESOURCE,
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, resourceRowId);
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.TARGET,
+ policyResource.getTarget());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE,
+ policyResource.getResourceType().name());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_ID,
+ policyResource.getResourceId());
+ },
+ policyResource,
+ false);
+ }
+
+ return null;
+ });
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_ADDING_POLICY, e);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy added with ID: " + policy.getId());
+ }
+ return policy;
+ }
+
+ @Override
+ public Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException {
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ jdbcTemplate.withTransaction(template -> {
+ template.executeUpdate(
+ PolicyMgtSQLConstants.Query.UPDATE_POLICY,
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policy.getName());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId());
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ });
+
+ template.executeUpdate(
+ PolicyMgtSQLConstants.Query.DELETE_POLICY_RESOURCES,
+ preparedStatement -> preparedStatement.setString(
+ PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId()));
+
+ for (PolicyResource policyResource : policy.getResources()) {
+ final String resourceRowId = UUID.randomUUID().toString();
+ template.executeInsert(
+ PolicyMgtSQLConstants.Query.ADD_POLICY_RESOURCE,
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, resourceRowId);
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.TARGET,
+ policyResource.getTarget());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE,
+ policyResource.getResourceType().name());
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_ID,
+ policyResource.getResourceId());
+ },
+ policyResource,
+ false);
+ }
+
+ return null;
+ });
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_UPDATING_POLICY, e);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy updated with ID: " + policy.getId());
+ }
+ return policy;
+ }
+
+ @Override
+ public void deletePolicy(String policyId, int tenantId) throws PolicyManagementException {
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ jdbcTemplate.withTransaction(template -> {
+ template.executeUpdate(
+ PolicyMgtSQLConstants.Query.DELETE_POLICY,
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policyId);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ });
+ return null;
+ });
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_DELETING_POLICY, e);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy deleted with ID: " + policyId);
+ }
+ }
+
+ @Override
+ public Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException {
+
+ String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId);
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ return jdbcTemplate.withTransaction(template -> {
+ Policy base = template.fetchSingleRecord(
+ PolicyMgtSQLConstants.Query.GET_POLICY_BY_ID,
+ (resultSet, rowNumber) -> new Policy(
+ resultSet.getString(PolicyMgtSQLConstants.Column.ID),
+ resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME),
+ tenantDomain, null),
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policyId);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ });
+ if (base == null) {
+ return null;
+ }
+ List resources = fetchPolicyResources(template, base.getId());
+ return new Policy(base.getId(), base.getName(), tenantDomain, resources);
+ });
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+
+ @Override
+ public Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException {
+
+ String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId);
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ return jdbcTemplate.withTransaction(template -> {
+ Policy base = template.fetchSingleRecord(
+ PolicyMgtSQLConstants.Query.GET_POLICY_BY_NAME,
+ (resultSet, rowNumber) -> new Policy(
+ resultSet.getString(PolicyMgtSQLConstants.Column.ID),
+ resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME),
+ tenantDomain, null),
+ preparedStatement -> {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policyName);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ });
+ if (base == null) {
+ return null;
+ }
+ List resources = fetchPolicyResources(template, base.getId());
+ return new Policy(base.getId(), base.getName(), tenantDomain, resources);
+ });
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+
+ @Override
+ public String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException {
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ return jdbcTemplate.withTransaction(
+ template -> template.fetchSingleRecord(
+ PolicyMgtSQLConstants.Query.CHECK_POLICY_NAME_EXISTS,
+ (resultSet, rowNumber) ->
+ resultSet.getString(PolicyMgtSQLConstants.Column.ID),
+ preparedStatement -> {
+ preparedStatement.setString(
+ PolicyMgtSQLConstants.Column.POLICY_NAME, policyName);
+ preparedStatement.setInt(
+ PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ }));
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+
+ @Override
+ public List getPolicies(int tenantId, String filter, int offset, int limit)
+ throws PolicyManagementException {
+
+ // FETCH NEXT 0 ROWS (MS SQL) is invalid and an empty page is meaningless, so short-circuit.
+ if (limit <= 0) {
+ return Collections.emptyList();
+ }
+ int safeOffset = Math.max(offset, 0);
+ boolean hasFilter = filter != null && !filter.trim().isEmpty();
+ String filterValue = hasFilter ? "%" + filter.trim() + "%" : null;
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ PaginationStyle style = resolvePaginationStyle();
+ String query = resolvePaginatedQuery(style, hasFilter);
+ List policies = jdbcTemplate., RuntimeException>withTransaction(
+ template -> template.executeQuery(
+ query,
+ (resultSet, rowNumber) -> new PolicyBasicInfo(
+ resultSet.getString(PolicyMgtSQLConstants.Column.ID),
+ resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME)),
+ preparedStatement -> {
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ if (hasFilter) {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.FILTER, filterValue);
+ }
+ bindPaginationParams(preparedStatement, style, safeOffset, limit);
+ }));
+ return policies != null ? policies : Collections.emptyList();
+ } catch (TransactionException | DataAccessException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+
+ @Override
+ public int getPolicyCount(int tenantId, String filter) throws PolicyManagementException {
+
+ boolean hasFilter = filter != null && !filter.trim().isEmpty();
+ String filterValue = hasFilter ? "%" + filter.trim() + "%" : null;
+ String query = hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_COUNT_FILTER
+ : PolicyMgtSQLConstants.Query.GET_POLICIES_COUNT;
+
+ NamedJdbcTemplate jdbcTemplate =
+ new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource());
+ try {
+ Integer count = jdbcTemplate.withTransaction(
+ template -> template.fetchSingleRecord(
+ query,
+ (resultSet, rowNumber) -> resultSet.getInt(1),
+ preparedStatement -> {
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId);
+ if (hasFilter) {
+ preparedStatement.setString(PolicyMgtSQLConstants.Column.FILTER, filterValue);
+ }
+ }));
+ return count != null ? count : 0;
+ } catch (TransactionException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+
+ /**
+ * Supported database-specific pagination dialects.
+ */
+ private enum PaginationStyle {
+ DEFAULT, MSSQL, ORACLE, DB2
+ }
+
+ private PaginationStyle resolvePaginationStyle() throws DataAccessException {
+
+ // H2, MySQL, MariaDB and PostgreSQL all accept the LIMIT ... OFFSET ... syntax (DEFAULT).
+ if (JdbcUtils.isOracleDB()) {
+ return PaginationStyle.ORACLE;
+ }
+ if (JdbcUtils.isDB2DB()) {
+ return PaginationStyle.DB2;
+ }
+ if (JdbcUtils.isMSSqlDB()) {
+ return PaginationStyle.MSSQL;
+ }
+ return PaginationStyle.DEFAULT;
+ }
+
+ private String resolvePaginatedQuery(PaginationStyle style, boolean hasFilter) {
+
+ switch (style) {
+ case ORACLE:
+ return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_ORACLE
+ : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_ORACLE;
+ case DB2:
+ return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_DB2
+ : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_DB2;
+ case MSSQL:
+ return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_MSSQL
+ : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_MSSQL;
+ default:
+ return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER
+ : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED;
+ }
+ }
+
+ private void bindPaginationParams(NamedPreparedStatement preparedStatement, PaginationStyle style,
+ int offset, int limit) throws SQLException {
+
+ switch (style) {
+ case ORACLE:
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.UPPER_BOUND, offset + limit);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset);
+ break;
+ case DB2:
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.LOWER_BOUND, offset + 1);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.UPPER_BOUND, offset + limit);
+ break;
+ case MSSQL:
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.LIMIT, limit);
+ break;
+ default:
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.LIMIT, limit);
+ preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset);
+ }
+ }
+
+ private List fetchPolicyResources(NamedTemplate template, String policyId)
+ throws DataAccessException {
+
+ List resources = template.executeQuery(
+ PolicyMgtSQLConstants.Query.GET_POLICY_RESOURCES,
+ (resultSet, rowNumber) -> new PolicyResource(
+ resultSet.getString(PolicyMgtSQLConstants.Column.ID),
+ resultSet.getString(PolicyMgtSQLConstants.Column.TARGET),
+ ResourceType.valueOf(resultSet.getString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE)),
+ resultSet.getString(PolicyMgtSQLConstants.Column.RESOURCE_ID),
+ null),
+ preparedStatement -> preparedStatement.setString(
+ PolicyMgtSQLConstants.Column.POLICY_ID, policyId));
+ return resources != null ? resources : Collections.emptyList();
+ }
+
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java
new file mode 100644
index 000000000000..4cb9b97add13
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.internal.service.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.api.service.PolicyEvaluationService;
+import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder;
+import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException;
+import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext;
+import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult;
+
+/**
+ * Default implementation of {@link PolicyEvaluationService}.
+ */
+public class PolicyEvaluationServiceImpl implements PolicyEvaluationService {
+
+ private static final Log LOG = LogFactory.getLog(PolicyEvaluationServiceImpl.class);
+
+ @Override
+ public RuleEvaluationResult evaluate(String policyName, String ruleSelector,
+ FlowContext flowContext, String tenantDomain)
+ throws PolicyManagementException, RuleEvaluationException {
+
+ Policy policy = PolicyMgtComponentServiceHolder.getInstance()
+ .getPolicyManagementService()
+ .getPolicyByName(policyName, tenantDomain);
+
+ if (policy == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Policy not found: " + policyName + " for tenant: " + tenantDomain);
+ }
+ return null;
+ }
+
+ if (ruleSelector == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Rule selector is null for policy '" + policyName + "' — treating as compliant.");
+ }
+ return new RuleEvaluationResult(null, true);
+ }
+
+ PolicyResource matchingResource = policy.getResources().stream()
+ .filter(r -> r.getResourceType() == ResourceType.RULE
+ && r.getTarget() != null
+ && ruleSelector.equalsIgnoreCase(r.getTarget()))
+ .findFirst()
+ .orElse(null);
+
+ if (matchingResource == null || matchingResource.getRule() == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("No rule for selector '" + ruleSelector + "' in policy '" + policyName
+ + "' — treating as compliant.");
+ }
+ return new RuleEvaluationResult(null, true);
+ }
+
+ return PolicyMgtComponentServiceHolder.getInstance()
+ .getRuleEvaluationService()
+ .evaluate(matchingResource.getRule().getId(), flowContext, tenantDomain);
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java
new file mode 100644
index 000000000000..0a6a92ab07a7
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
+ *
+ * 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.policy.management.internal.service.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
+import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService;
+import org.wso2.carbon.identity.policy.management.api.util.PolicyManagementExceptionHandler;
+import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder;
+import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO;
+import org.wso2.carbon.identity.policy.management.internal.dao.impl.CacheBackedPolicyManagementDAO;
+import org.wso2.carbon.identity.policy.management.internal.dao.impl.PolicyManagementDAOImpl;
+import org.wso2.carbon.identity.rule.management.api.exception.RuleManagementException;
+import org.wso2.carbon.identity.rule.management.api.model.Rule;
+import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Implementation of Policy Management Service.
+ * Orchestrates rule-mgt service calls and best-effort saga compensation around the DAO layer.
+ */
+public class PolicyManagementServiceImpl implements PolicyManagementService {
+
+ private static final Log LOG = LogFactory.getLog(PolicyManagementServiceImpl.class);
+ private final PolicyManagementDAO policyManagementDAO;
+
+ /**
+ * Default constructor used by OSGi component. Delegates to the DAO-backed constructor.
+ */
+ public PolicyManagementServiceImpl() {
+
+ this(new CacheBackedPolicyManagementDAO(new PolicyManagementDAOImpl()));
+ }
+
+ /**
+ * Constructor for tests or manual instantiation with a custom DAO.
+ *
+ * @param policyManagementDAO DAO implementation to use.
+ */
+ public PolicyManagementServiceImpl(PolicyManagementDAO policyManagementDAO) {
+
+ this.policyManagementDAO = policyManagementDAO;
+ }
+
+ @Override
+ public Policy addPolicy(Policy policy, String tenantDomain) throws PolicyManagementException {
+
+ validatePolicyFields(policy);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Creating policy with name: %s for tenant: %s",
+ policy.getName(), tenantDomain));
+ }
+ int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
+ validateUniquePolicyName(policy.getName(), null, tenantId);
+
+ Policy policyWithId = new Policy(
+ UUID.randomUUID().toString(),
+ policy.getName(),
+ tenantDomain,
+ policy.getResources());
+
+ RuleManagementService ruleManagementService =
+ PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService();
+ List createdRuleIds = new ArrayList<>();
+ List resourcesWithIds = new ArrayList<>();
+
+ try {
+ for (PolicyResource pr : policyWithId.getResources()) {
+ if (pr.getResourceType() != ResourceType.RULE) {
+ // ACTION (and future types): resourceId already references an existing resource.
+ resourcesWithIds.add(pr);
+ continue;
+ }
+ Rule createdRule = ruleManagementService.addRule(pr.getRule(), tenantDomain);
+ createdRuleIds.add(createdRule.getId());
+ resourcesWithIds.add(new PolicyResource(
+ pr.getId(), pr.getTarget(), ResourceType.RULE, createdRule.getId(), null));
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Rule added for policy target '" + pr.getTarget()
+ + "' with ruleId: " + createdRule.getId());
+ }
+ }
+ } catch (RuleManagementException e) {
+ compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService);
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_ADDING_RULE_FOR_POLICY, e, policyWithId.getName());
+ }
+
+ Policy policyWithResourceIds = new Policy(
+ policyWithId.getId(), policyWithId.getName(), tenantDomain, resourcesWithIds);
+
+ try {
+ return policyManagementDAO.addPolicy(policyWithResourceIds, tenantId);
+ } catch (PolicyManagementException e) {
+ compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService);
+ throw e;
+ }
+ }
+
+ @Override
+ public Policy updatePolicy(Policy policy, String tenantDomain) throws PolicyManagementException {
+
+ validatePolicyFields(policy);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Updating policy with ID: %s for tenant: %s",
+ policy.getId(), tenantDomain));
+ }
+ int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
+ Policy existingPolicy = policyManagementDAO.getPolicyById(policy.getId(), tenantId);
+ if (existingPolicy == null) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_POLICY_NOT_FOUND, policy.getId());
+ }
+ validateUniquePolicyName(policy.getName(), policy.getId(), tenantId);
+
+ RuleManagementService ruleManagementService =
+ PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService();
+
+ // Create the new rules first so the old rules remain intact until the DB commit succeeds. The old rules
+ // are only deleted after the policy is durably updated, keeping the operation recoverable on any failure.
+ List createdRuleIds = new ArrayList<>();
+ List resourcesWithIds = new ArrayList<>();
+
+ try {
+ for (PolicyResource pr : policy.getResources()) {
+ if (pr.getResourceType() != ResourceType.RULE) {
+ // ACTION (and future types): resourceId already references an existing resource.
+ resourcesWithIds.add(pr);
+ continue;
+ }
+ Rule createdRule = ruleManagementService.addRule(pr.getRule(), tenantDomain);
+ createdRuleIds.add(createdRule.getId());
+ resourcesWithIds.add(new PolicyResource(
+ pr.getId(), pr.getTarget(), ResourceType.RULE, createdRule.getId(), null));
+ }
+ } catch (RuleManagementException e) {
+ compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService);
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_UPDATING_RULE_FOR_POLICY, e, policy.getId());
+ }
+
+ Policy policyWithResourceIds = new Policy(
+ policy.getId(), policy.getName(), tenantDomain, resourcesWithIds);
+
+ Policy updatedPolicy;
+ try {
+ updatedPolicy = policyManagementDAO.updatePolicy(policyWithResourceIds, tenantId);
+ } catch (PolicyManagementException e) {
+ compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService);
+ throw e;
+ }
+
+ // DB commit succeeded; the old rules are now safe to remove (best-effort).
+ deleteRulesFromRuleManagementService(existingPolicy.getResources(), tenantDomain, ruleManagementService,
+ policy.getId());
+
+ return updatedPolicy;
+ }
+
+ @Override
+ public void deletePolicy(String policyId, String tenantDomain) throws PolicyManagementException {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Deleting policy with ID: %s for tenant: %s",
+ policyId, tenantDomain));
+ }
+ int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
+ RuleManagementService ruleManagementService =
+ PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService();
+ Policy existingPolicy = policyManagementDAO.getPolicyById(policyId, tenantId);
+ if (existingPolicy == null) {
+ return;
+ }
+ policyManagementDAO.deletePolicy(policyId, tenantId);
+ deleteRulesFromRuleManagementService(existingPolicy.getResources(), tenantDomain, ruleManagementService,
+ policyId);
+ }
+
+ @Override
+ public Policy getPolicyById(String policyId, String tenantDomain) throws PolicyManagementException {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Retrieving policy with ID: %s for tenant: %s",
+ policyId, tenantDomain));
+ }
+ int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
+ Policy policy = policyManagementDAO.getPolicyById(policyId, tenantId);
+ if (policy == null) {
+ return null;
+ }
+ return hydrateResources(policy, tenantDomain);
+ }
+
+ @Override
+ public Policy getPolicyByName(String policyName, String tenantDomain) throws PolicyManagementException {
+
+ if (policyName == null || policyName.trim().isEmpty()) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy name");
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Retrieving policy with name: %s for tenant: %s",
+ policyName, tenantDomain));
+ }
+ int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
+ Policy policy = policyManagementDAO.getPolicyByName(policyName, tenantId);
+ if (policy == null) {
+ return null;
+ }
+ return hydrateResources(policy, tenantDomain);
+ }
+
+ @Override
+ public List getPolicies(String tenantDomain, String filter, int offset, int limit)
+ throws PolicyManagementException {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Listing policies for tenant: %s with filter: %s, offset: %d, limit: %d",
+ tenantDomain, filter, offset, limit));
+ }
+ return policyManagementDAO.getPolicies(
+ IdentityTenantUtil.getTenantId(tenantDomain), filter, offset, limit);
+ }
+
+ @Override
+ public int getPolicyCount(String tenantDomain, String filter) throws PolicyManagementException {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Counting policies for tenant: %s with filter: %s", tenantDomain, filter));
+ }
+ return policyManagementDAO.getPolicyCount(IdentityTenantUtil.getTenantId(tenantDomain), filter);
+ }
+
+ // Only RULE resources are hydrated; actions are referenced by id and need no hydration here.
+ private Policy hydrateResources(Policy policy, String tenantDomain) throws PolicyManagementException {
+
+ List hydratedResources = new ArrayList<>();
+ for (PolicyResource pr : policy.getResources()) {
+ if (pr.getResourceType() != ResourceType.RULE) {
+ hydratedResources.add(pr);
+ continue;
+ }
+ try {
+ Rule rule = PolicyMgtComponentServiceHolder.getInstance()
+ .getRuleManagementService()
+ .getRuleByRuleId(pr.getResourceId(), tenantDomain);
+ hydratedResources.add(new PolicyResource(
+ pr.getId(), pr.getTarget(), ResourceType.RULE, pr.getResourceId(), rule));
+ } catch (RuleManagementException e) {
+ throw PolicyManagementExceptionHandler.handleServerException(
+ ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e);
+ }
+ }
+ return new Policy(policy.getId(), policy.getName(), policy.getTenantDomain(), hydratedResources);
+ }
+
+ private void deleteRulesFromRuleManagementService(List resources, String tenantDomain,
+ RuleManagementService ruleManagementService,
+ String policyId) {
+
+ for (PolicyResource pr : resources) {
+ if (pr.getResourceType() != ResourceType.RULE) {
+ continue;
+ }
+ try {
+ ruleManagementService.deleteRule(pr.getResourceId(), tenantDomain);
+ } catch (RuleManagementException e) {
+ LOG.error("Failed to delete rule " + pr.getResourceId()
+ + " from rule-mgt for policy " + policyId + ". Rule may be orphaned.", e);
+ }
+ }
+ }
+
+ private void compensateCreatedRules(List ruleIds, String tenantDomain,
+ RuleManagementService ruleManagementService) {
+
+ for (String ruleId : ruleIds) {
+ try {
+ ruleManagementService.deleteRule(ruleId, tenantDomain);
+ } catch (RuleManagementException ex) {
+ LOG.error("Saga compensation failed: could not delete rule " + ruleId
+ + " from rule-mgt after policy persistence failure.", ex);
+ }
+ }
+ }
+
+ private void validatePolicyFields(Policy policy) throws PolicyManagementClientException {
+
+ if (policy == null) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy");
+ }
+ if (policy.getName() == null || policy.getName().trim().isEmpty()) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy name");
+ }
+ validateUniqueTargetsPerResourceType(policy);
+ }
+
+ private void validateUniqueTargetsPerResourceType(Policy policy) throws PolicyManagementClientException {
+
+ Set seenTargets = new HashSet<>();
+ for (PolicyResource resource : policy.getResources()) {
+ if (resource == null || resource.getTarget() == null) {
+ continue;
+ }
+ if (resource.getResourceType() == null) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Resource type");
+ }
+ String key = resource.getResourceType().name() + "|"
+ + resource.getTarget().toLowerCase(Locale.ROOT);
+ if (!seenTargets.add(key)) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_DUPLICATE_PLATFORM_IN_POLICY,
+ policy.getName(), resource.getTarget());
+ }
+ }
+ }
+
+ private void validateUniquePolicyName(String name, String excludePolicyId, int tenantId)
+ throws PolicyManagementException {
+
+ String existingId = policyManagementDAO.getPolicyIdByName(name, tenantId);
+ if (existingId != null && !existingId.equals(excludePolicyId)) {
+ throw PolicyManagementExceptionHandler.handleClientException(
+ ErrorMessage.ERROR_POLICY_ALREADY_EXISTS, name);
+ }
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java
new file mode 100644
index 000000000000..298049b7778e
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.dao;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithRealmService;
+import org.wso2.carbon.identity.core.internal.component.IdentityCoreServiceDataHolder;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCache;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheEntry;
+import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheKey;
+import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO;
+import org.wso2.carbon.identity.policy.management.internal.dao.impl.CacheBackedPolicyManagementDAO;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+/**
+ * Unit tests for CacheBackedPolicyManagementDAO.
+ * Verifies name-only caching, pass-through for ID lookups, and precise cache invalidation on writes.
+ */
+@WithCarbonHome
+@WithRealmService(injectToSingletons = {IdentityCoreServiceDataHolder.class})
+public class CacheBackedPolicyManagementDAOTest {
+
+ private static final String POLICY_ID = "policyId";
+ private static final String POLICY_NAME = "TestPolicy";
+ private static final String TENANT_DOMAIN = "carbon.super";
+ private static final int TENANT_ID = 1;
+
+ private PolicyManagementDAO policyManagementDAO;
+ private CacheBackedPolicyManagementDAO cacheBackedPolicyManagementDAO;
+ private PolicyCache policyCache;
+
+ @BeforeClass
+ public void setUpClass() {
+
+ policyCache = PolicyCache.getInstance();
+ }
+
+ @BeforeMethod
+ public void setUp() {
+
+ policyManagementDAO = mock(PolicyManagementDAO.class);
+ cacheBackedPolicyManagementDAO = new CacheBackedPolicyManagementDAO(policyManagementDAO);
+ policyCache.clear(TENANT_ID);
+ }
+
+ private Policy policy() {
+
+ return new Policy(POLICY_ID, POLICY_NAME, TENANT_DOMAIN, Collections.emptyList());
+ }
+
+ @Test
+ public void testAddPolicyDoesNotCache() throws PolicyManagementException {
+
+ Policy policy = policy();
+
+ cacheBackedPolicyManagementDAO.addPolicy(policy, TENANT_ID);
+
+ verify(policyManagementDAO).addPolicy(policy, TENANT_ID);
+ assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID));
+ }
+
+ @Test
+ public void testGetPolicyByNameCacheMissPopulatesNameEntry() throws PolicyManagementException {
+
+ Policy policy = policy();
+ when(policyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID)).thenReturn(policy);
+
+ Policy result = cacheBackedPolicyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID);
+
+ assertEquals(result, policy);
+ verify(policyManagementDAO).getPolicyByName(POLICY_NAME, TENANT_ID);
+ assertEquals(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID).getPolicy(),
+ policy);
+ }
+
+ @Test
+ public void testGetPolicyByNameCacheHit() throws PolicyManagementException {
+
+ Policy policy = policy();
+ policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID);
+
+ Policy result = cacheBackedPolicyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID);
+
+ assertEquals(result, policy);
+ verify(policyManagementDAO, never()).getPolicyByName(POLICY_NAME, TENANT_ID);
+ }
+
+ @Test
+ public void testGetPolicyByIdAlwaysDelegates() throws PolicyManagementException {
+
+ Policy policy = policy();
+ when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy);
+
+ Policy first = cacheBackedPolicyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID);
+ Policy second = cacheBackedPolicyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID);
+
+ assertEquals(first, policy);
+ assertEquals(second, policy);
+ // Both calls must go to the underlying DAO — getPolicyById is never cached.
+ verify(policyManagementDAO, times(2)).getPolicyById(POLICY_ID, TENANT_ID);
+ }
+
+ @Test
+ public void testUpdatePolicyClearsNameEntry() throws PolicyManagementException {
+
+ Policy policy = policy();
+ policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID);
+ when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy);
+ when(policyManagementDAO.updatePolicy(policy, TENANT_ID)).thenReturn(policy);
+
+ cacheBackedPolicyManagementDAO.updatePolicy(policy, TENANT_ID);
+
+ verify(policyManagementDAO).updatePolicy(policy, TENANT_ID);
+ assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID));
+ }
+
+ @Test
+ public void testUpdatePolicyRenamesClearsBothOldAndNewName() throws PolicyManagementException {
+
+ String oldName = "OldPolicy";
+ Policy existing = new Policy(POLICY_ID, oldName, TENANT_DOMAIN, Collections.emptyList());
+ Policy renamed = new Policy(POLICY_ID, POLICY_NAME, TENANT_DOMAIN, Collections.emptyList());
+ policyCache.addToCacheOnRead(new PolicyCacheKey(oldName), new PolicyCacheEntry(existing), TENANT_ID);
+ policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(existing), TENANT_ID);
+ when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(existing);
+ when(policyManagementDAO.updatePolicy(renamed, TENANT_ID)).thenReturn(renamed);
+
+ cacheBackedPolicyManagementDAO.updatePolicy(renamed, TENANT_ID);
+
+ assertNull(policyCache.getValueFromCache(new PolicyCacheKey(oldName), TENANT_ID));
+ assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID));
+ }
+
+ @Test
+ public void testDeletePolicyClearsNameEntry() throws PolicyManagementException {
+
+ Policy policy = policy();
+ policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID);
+ when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy);
+
+ cacheBackedPolicyManagementDAO.deletePolicy(POLICY_ID, TENANT_ID);
+
+ verify(policyManagementDAO).deletePolicy(POLICY_ID, TENANT_ID);
+ assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID));
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java
new file mode 100644
index 000000000000..ea428b6c196a
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.dao;
+
+import org.mockito.MockedStatic;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithH2Database;
+import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.internal.dao.impl.PolicyManagementDAOImpl;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static org.mockito.Mockito.mockStatic;
+
+/**
+ * Unit tests for PolicyManagementDAOImpl.
+ */
+@WithCarbonHome
+@WithH2Database(files = {"dbscripts/h2.sql"})
+public class PolicyManagementDAOImplTest {
+
+ private static final int TENANT_ID = -1234;
+ private static final String TENANT_DOMAIN = "carbon.super";
+ private static final String TEST_POLICY_NAME = "TestPolicy";
+ private static final String TEST_RULE_ID = UUID.randomUUID().toString();
+
+ private PolicyManagementDAOImpl policyManagementDAO;
+ private MockedStatic identityTenantUtil;
+ private String createdPolicyId;
+
+ @BeforeClass
+ public void setUp() {
+
+ policyManagementDAO = new PolicyManagementDAOImpl();
+ identityTenantUtil = mockStatic(IdentityTenantUtil.class);
+ identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(TENANT_ID))
+ .thenReturn(TENANT_DOMAIN);
+ identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(TENANT_DOMAIN))
+ .thenReturn(TENANT_ID);
+ }
+
+ @AfterClass
+ public void tearDown() {
+
+ identityTenantUtil.close();
+ }
+
+ @Test(priority = 1)
+ public void testAddPolicy() throws PolicyManagementException {
+
+ List resources = Collections.singletonList(
+ new PolicyResource(null, "android", ResourceType.RULE, TEST_RULE_ID, null));
+
+ Policy policy = new Policy(
+ UUID.randomUUID().toString(),
+ TEST_POLICY_NAME,
+ TENANT_DOMAIN,
+ resources);
+
+ Policy result = policyManagementDAO.addPolicy(policy, TENANT_ID);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getName(), TEST_POLICY_NAME);
+ Assert.assertEquals(result.getResources().size(), 1);
+ Assert.assertEquals(result.getResources().get(0).getTarget(), "android");
+
+ createdPolicyId = result.getId();
+ }
+
+ @Test(priority = 2, dependsOnMethods = {"testAddPolicy"})
+ public void testGetPolicyById() throws PolicyManagementException {
+
+ Policy result = policyManagementDAO.getPolicyById(createdPolicyId, TENANT_ID);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getId(), createdPolicyId);
+ Assert.assertEquals(result.getName(), TEST_POLICY_NAME);
+ Assert.assertEquals(result.getResources().size(), 1);
+ Assert.assertEquals(result.getResources().get(0).getResourceId(), TEST_RULE_ID);
+ }
+
+ @Test(priority = 3, dependsOnMethods = {"testAddPolicy"})
+ public void testUpdatePolicy() throws PolicyManagementException {
+
+ String updatedName = "UpdatedPolicy";
+ String updatedRuleId = UUID.randomUUID().toString();
+
+ List updatedResources = Collections.singletonList(
+ new PolicyResource(null, "ios", ResourceType.RULE, updatedRuleId, null));
+
+ Policy updatedPolicy = new Policy(createdPolicyId, updatedName, TENANT_DOMAIN, updatedResources);
+
+ Policy result = policyManagementDAO.updatePolicy(updatedPolicy, TENANT_ID);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getName(), updatedName);
+ Assert.assertEquals(result.getResources().size(), 1);
+ Assert.assertEquals(result.getResources().get(0).getTarget(), "ios");
+ }
+
+ @Test(priority = 4, dependsOnMethods = {"testAddPolicy"})
+ public void testGetPolicyById_NotFound() throws PolicyManagementException {
+
+ Policy result = policyManagementDAO.getPolicyById(
+ UUID.randomUUID().toString(), TENANT_ID);
+
+ Assert.assertNull(result);
+ }
+
+ @Test(priority = 5, dependsOnMethods = {"testAddPolicy"})
+ public void testDeletePolicy() throws PolicyManagementException {
+
+ policyManagementDAO.deletePolicy(createdPolicyId, TENANT_ID);
+
+ Policy result = policyManagementDAO.getPolicyById(createdPolicyId, TENANT_ID);
+ Assert.assertNull(result);
+ }
+
+ private String addPolicy(String name) throws PolicyManagementException {
+
+ Policy policy = new Policy(UUID.randomUUID().toString(), name, TENANT_DOMAIN, Collections.emptyList());
+ return policyManagementDAO.addPolicy(policy, TENANT_ID).getId();
+ }
+
+ @Test(priority = 6)
+ public void testGetPoliciesPaginationAndCount() throws PolicyManagementException {
+
+ addPolicy("PageTestAlpha");
+ addPolicy("PageTestBeta");
+ addPolicy("PageTestGamma");
+
+ Assert.assertEquals(policyManagementDAO.getPolicyCount(TENANT_ID, "PageTest"), 3);
+
+ List firstPage = policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 0, 2);
+ Assert.assertEquals(firstPage.size(), 2);
+ // Results are ordered by policy name ascending.
+ Assert.assertEquals(firstPage.get(0).getName(), "PageTestAlpha");
+ Assert.assertEquals(firstPage.get(1).getName(), "PageTestBeta");
+
+ List secondPage = policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 2, 2);
+ Assert.assertEquals(secondPage.size(), 1);
+ Assert.assertEquals(secondPage.get(0).getName(), "PageTestGamma");
+
+ // A non-positive limit yields an empty page.
+ Assert.assertTrue(policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 0, 0).isEmpty());
+ }
+
+ @Test(priority = 7)
+ public void testGetPoliciesWithFilter() throws PolicyManagementException {
+
+ addPolicy("FilterTestUnique");
+
+ Assert.assertEquals(policyManagementDAO.getPolicyCount(TENANT_ID, "FilterTestUnique"), 1);
+ List result = policyManagementDAO.getPolicies(TENANT_ID, "FilterTestUnique", 0, 10);
+ Assert.assertEquals(result.size(), 1);
+ Assert.assertEquals(result.get(0).getName(), "FilterTestUnique");
+ }
+
+ @Test(priority = 8)
+ public void testGetPolicyByName() throws PolicyManagementException {
+
+ addPolicy("NameLookupPolicy");
+
+ Policy result = policyManagementDAO.getPolicyByName("NameLookupPolicy", TENANT_ID);
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getName(), "NameLookupPolicy");
+ }
+
+ @Test(priority = 9)
+ public void testResourceRoundTripAndTypeStored() throws PolicyManagementException {
+
+ String ruleId = UUID.randomUUID().toString();
+ Policy policy = new Policy(UUID.randomUUID().toString(), "ResourceRoundTrip", TENANT_DOMAIN,
+ Collections.singletonList(new PolicyResource(null, "ios", ResourceType.RULE, ruleId, null)));
+ String id = policyManagementDAO.addPolicy(policy, TENANT_ID).getId();
+
+ Policy fetched = policyManagementDAO.getPolicyById(id, TENANT_ID);
+ Assert.assertEquals(fetched.getResources().size(), 1);
+ PolicyResource resource = fetched.getResources().get(0);
+ Assert.assertEquals(resource.getResourceType(), ResourceType.RULE);
+ Assert.assertEquals(resource.getTarget(), "ios");
+ Assert.assertEquals(resource.getResourceId(), ruleId);
+ }
+
+ @Test(priority = 10, expectedExceptions = PolicyManagementException.class)
+ public void testDuplicateTargetPerTypeRejected() throws PolicyManagementException {
+
+ // Two RULE resources for the same target violate UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE).
+ Policy policy = new Policy(UUID.randomUUID().toString(), "DuplicateTargetPolicy", TENANT_DOMAIN,
+ Arrays.asList(
+ new PolicyResource(null, "ios", ResourceType.RULE, UUID.randomUUID().toString(), null),
+ new PolicyResource(null, "ios", ResourceType.RULE, UUID.randomUUID().toString(), null)));
+
+ policyManagementDAO.addPolicy(policy, TENANT_ID);
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java
new file mode 100644
index 000000000000..8b549db744e5
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.service;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService;
+import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder;
+import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyEvaluationServiceImpl;
+import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException;
+import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext;
+import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult;
+import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService;
+import org.wso2.carbon.identity.rule.management.api.model.Rule;
+
+import java.util.Collections;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for PolicyEvaluationServiceImpl.
+ */
+public class PolicyEvaluationServiceImplTest {
+
+ private static final String POLICY_NAME = "TestPolicy";
+ private static final String TENANT_DOMAIN = "carbon.super";
+ private static final String RULE_ID = UUID.randomUUID().toString();
+
+ private PolicyManagementService policyManagementService;
+ private RuleEvaluationService ruleEvaluationService;
+ private PolicyEvaluationServiceImpl policyEvaluationService;
+ private FlowContext flowContext;
+
+ @BeforeMethod
+ public void setUp() {
+
+ policyManagementService = mock(PolicyManagementService.class);
+ ruleEvaluationService = mock(RuleEvaluationService.class);
+ PolicyMgtComponentServiceHolder.getInstance().setPolicyManagementService(policyManagementService);
+ PolicyMgtComponentServiceHolder.getInstance().setRuleEvaluationService(ruleEvaluationService);
+ policyEvaluationService = new PolicyEvaluationServiceImpl();
+ flowContext = mock(FlowContext.class);
+ }
+
+ private Policy policyWithRule(String target) {
+
+ Rule rule = mock(Rule.class);
+ when(rule.getId()).thenReturn(RULE_ID);
+ PolicyResource resource = new PolicyResource(
+ UUID.randomUUID().toString(), target, ResourceType.RULE, RULE_ID, rule);
+ return new Policy(UUID.randomUUID().toString(), POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(resource));
+ }
+
+ @Test
+ public void testPolicyNotFoundReturnsNull() throws PolicyManagementException, RuleEvaluationException {
+
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(null);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, "ios", flowContext, TENANT_DOMAIN);
+
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void testNoMatchingResourceReturnsCompliant() throws PolicyManagementException, RuleEvaluationException {
+
+ Policy policy = policyWithRule("android");
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, "ios", flowContext, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertTrue(result.isRuleSatisfied());
+ verify(ruleEvaluationService, org.mockito.Mockito.never())
+ .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN));
+ }
+
+ @Test
+ public void testMatchingRuleDelegatesToRuleEvaluation() throws PolicyManagementException, RuleEvaluationException {
+
+ Policy policy = policyWithRule("ios");
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy);
+ RuleEvaluationResult expected = mock(RuleEvaluationResult.class);
+ when(ruleEvaluationService.evaluate(RULE_ID, flowContext, TENANT_DOMAIN)).thenReturn(expected);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, "ios", flowContext, TENANT_DOMAIN);
+
+ Assert.assertEquals(result, expected);
+ verify(ruleEvaluationService).evaluate(RULE_ID, flowContext, TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testNullSelectorReturnsCompliant() throws PolicyManagementException, RuleEvaluationException {
+
+ Policy policy = policyWithRule("ios");
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, null, flowContext, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertTrue(result.isRuleSatisfied());
+ verify(ruleEvaluationService, org.mockito.Mockito.never())
+ .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN));
+ }
+
+ @Test
+ public void testResourceWithNullTargetIsSkipped() throws PolicyManagementException, RuleEvaluationException {
+
+ Rule rule = mock(Rule.class);
+ when(rule.getId()).thenReturn(RULE_ID);
+ // Resource with a null target — the filter must not NPE on equalsIgnoreCase.
+ PolicyResource nullTargetResource = new PolicyResource(
+ UUID.randomUUID().toString(), null, ResourceType.RULE, RULE_ID, rule);
+ Policy policy = new Policy(UUID.randomUUID().toString(), POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(nullTargetResource));
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, "ios", flowContext, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertTrue(result.isRuleSatisfied());
+ verify(ruleEvaluationService, org.mockito.Mockito.never())
+ .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN));
+ }
+
+ @Test
+ public void testSelectorMatchIsCaseInsensitive() throws PolicyManagementException, RuleEvaluationException {
+
+ Policy policy = policyWithRule("iOS");
+ when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy);
+ RuleEvaluationResult expected = mock(RuleEvaluationResult.class);
+ when(ruleEvaluationService.evaluate(RULE_ID, flowContext, TENANT_DOMAIN)).thenReturn(expected);
+
+ RuleEvaluationResult result = policyEvaluationService.evaluate(
+ POLICY_NAME, "ios", flowContext, TENANT_DOMAIN);
+
+ Assert.assertEquals(result, expected);
+ verify(ruleEvaluationService).evaluate(RULE_ID, flowContext, TENANT_DOMAIN);
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java
new file mode 100644
index 000000000000..31abda688c15
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2026, 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.policy.management.service;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithRealmService;
+import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException;
+import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementServerException;
+import org.wso2.carbon.identity.policy.management.api.model.Policy;
+import org.wso2.carbon.identity.policy.management.api.model.PolicyResource;
+import org.wso2.carbon.identity.policy.management.api.model.ResourceType;
+import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder;
+import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO;
+import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyManagementServiceImpl;
+import org.wso2.carbon.identity.rule.management.api.exception.RuleManagementException;
+import org.wso2.carbon.identity.rule.management.api.model.Rule;
+import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for PolicyManagementServiceImpl.
+ * Covers validation, rule-mgt orchestration, and best-effort saga compensation.
+ */
+@WithCarbonHome
+@WithRealmService
+public class PolicyManagementServiceImplTest {
+
+ private static final String TENANT_DOMAIN = "carbon.super";
+ private static final int TENANT_ID = -1234;
+ private static final String TEST_POLICY_NAME = "TestPolicy";
+ private static final String TEST_POLICY_ID = UUID.randomUUID().toString();
+
+ @Mock
+ private PolicyManagementDAO policyManagementDAO;
+
+ @Mock
+ private RuleManagementService ruleManagementService;
+
+ private PolicyManagementServiceImpl policyManagementService;
+ private MockedStatic identityTenantUtil;
+ private AutoCloseable mocks;
+ // Reflection fields removed; tests now construct the service with a mock DAO.
+
+ @BeforeClass
+ public void setUp() throws Exception {
+
+ mocks = MockitoAnnotations.openMocks(this);
+ policyManagementService = new PolicyManagementServiceImpl(policyManagementDAO);
+
+ PolicyMgtComponentServiceHolder.getInstance().setRuleManagementService(ruleManagementService);
+
+ identityTenantUtil = mockStatic(IdentityTenantUtil.class);
+ identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(TENANT_DOMAIN)).thenReturn(TENANT_ID);
+ identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(TENANT_ID)).thenReturn(TENANT_DOMAIN);
+ }
+
+ @AfterClass
+ public void tearDown() throws Exception {
+ identityTenantUtil.close();
+ mocks.close();
+ }
+
+ @BeforeMethod
+ public void reset() {
+
+ org.mockito.Mockito.reset(policyManagementDAO);
+ org.mockito.Mockito.reset(ruleManagementService);
+ }
+
+ // --- Basic validation tests ---
+
+ @Test
+ public void testAddPolicy() throws PolicyManagementException {
+
+ Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+ Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+
+ when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy);
+
+ Policy result = policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getName(), TEST_POLICY_NAME);
+ verify(policyManagementDAO).addPolicy(any(Policy.class), eq(TENANT_ID));
+ }
+
+ @Test(expectedExceptions = PolicyManagementClientException.class)
+ public void testAddPolicy_EmptyName() throws PolicyManagementException {
+
+ Policy inputPolicy = new Policy(null, "", TENANT_DOMAIN,
+ Collections.emptyList());
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+ }
+
+ @Test(expectedExceptions = PolicyManagementClientException.class)
+ public void testAddPolicy_NullName() throws PolicyManagementException {
+
+ Policy inputPolicy = new Policy(null, null, TENANT_DOMAIN,
+ Collections.emptyList());
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testGetPolicyById() throws PolicyManagementException {
+
+ Policy expectedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(expectedPolicy);
+
+ Policy result = policyManagementService.getPolicyById(TEST_POLICY_ID, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getId(), TEST_POLICY_ID);
+ Assert.assertEquals(result.getName(), TEST_POLICY_NAME);
+ verify(policyManagementDAO).getPolicyById(TEST_POLICY_ID, TENANT_ID);
+ }
+
+ @Test
+ public void testUpdatePolicy() throws PolicyManagementException {
+
+ Policy existingPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+ Policy updatedPolicy = new Policy(TEST_POLICY_ID, "UpdatedPolicy", TENANT_DOMAIN,
+ Collections.emptyList());
+
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existingPolicy);
+ when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(updatedPolicy);
+
+ Policy result = policyManagementService.updatePolicy(updatedPolicy, TENANT_DOMAIN);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.getName(), "UpdatedPolicy");
+ verify(policyManagementDAO).updatePolicy(any(Policy.class), eq(TENANT_ID));
+ }
+
+ @Test(expectedExceptions = PolicyManagementClientException.class)
+ public void testUpdatePolicy_PolicyNotFound() throws PolicyManagementException {
+
+ Policy policy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(null);
+
+ policyManagementService.updatePolicy(policy, TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testDeletePolicy() throws PolicyManagementException {
+
+ Policy existingPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.emptyList());
+
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existingPolicy);
+
+ policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN);
+
+ verify(policyManagementDAO).deletePolicy(TEST_POLICY_ID, TENANT_ID);
+ }
+
+ @Test
+ public void testDeletePolicy_PolicyNotExists() throws PolicyManagementException {
+
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(null);
+
+ policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN);
+
+ verify(policyManagementDAO, org.mockito.Mockito.never()).deletePolicy(any(), eq(TENANT_ID));
+ }
+
+ // --- Rule orchestration and saga compensation tests ---
+
+ @Test
+ public void testAddPolicyWithRuleResource_CreatesRuleAndPersistsWithResourceId()
+ throws PolicyManagementException, RuleManagementException {
+
+ Rule created = mock(Rule.class);
+ when(created.getId()).thenReturn("rule-1");
+ when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created);
+
+ PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class));
+ Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(ruleRes));
+ Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(ruleRes));
+ when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy);
+
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+
+ verify(ruleManagementService).addRule(any(Rule.class), eq(TENANT_DOMAIN));
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class);
+ verify(policyManagementDAO).addPolicy(captor.capture(), eq(TENANT_ID));
+ PolicyResource persisted = captor.getValue().getResources().get(0);
+ Assert.assertEquals(persisted.getResourceType(), ResourceType.RULE);
+ Assert.assertEquals(persisted.getResourceId(), "rule-1");
+ Assert.assertEquals(persisted.getTarget(), "ios");
+ }
+
+ @Test
+ public void testAddPolicyWithActionResource_PassesThrough()
+ throws PolicyManagementException, RuleManagementException {
+
+ PolicyResource action = new PolicyResource(null, "ios", ResourceType.ACTION, "action-1", null);
+ Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(action));
+ Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(action));
+ when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy);
+
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+
+ // ACTION resources are not created through rule-mgt; they pass through unchanged.
+ verify(ruleManagementService, never()).addRule(any(Rule.class), eq(TENANT_DOMAIN));
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class);
+ verify(policyManagementDAO).addPolicy(captor.capture(), eq(TENANT_ID));
+ PolicyResource persisted = captor.getValue().getResources().get(0);
+ Assert.assertEquals(persisted.getResourceType(), ResourceType.ACTION);
+ Assert.assertEquals(persisted.getResourceId(), "action-1");
+ }
+
+ @Test
+ public void testAddPolicyWithRule_CompensatesWhenRuleCreationFails()
+ throws RuleManagementException, PolicyManagementException {
+
+ Rule created = mock(Rule.class);
+ when(created.getId()).thenReturn("rule-1");
+ when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN)))
+ .thenReturn(created)
+ .thenThrow(RuleManagementException.class);
+
+ PolicyResource r1 = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class));
+ PolicyResource r2 = new PolicyResource(null, "android", ResourceType.RULE, null, mock(Rule.class));
+ Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, Arrays.asList(r1, r2));
+
+ try {
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+ Assert.fail("Expected PolicyManagementException");
+ } catch (PolicyManagementException expected) {
+ // Expected.
+ }
+
+ // The first rule succeeded; it must be compensated after the second rule creation fails.
+ verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN);
+ verify(policyManagementDAO, never()).addPolicy(any(Policy.class), eq(TENANT_ID));
+ }
+
+ @Test
+ public void testAddPolicyWithRule_CompensatesWhenPersistenceFails()
+ throws RuleManagementException, PolicyManagementException {
+
+ Rule created = mock(Rule.class);
+ when(created.getId()).thenReturn("rule-1");
+ when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created);
+ when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID)))
+ .thenThrow(PolicyManagementServerException.class);
+
+ PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class));
+ Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(ruleRes));
+
+ try {
+ policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN);
+ Assert.fail("Expected PolicyManagementException");
+ } catch (PolicyManagementException expected) {
+ // Expected.
+ }
+
+ // The rule created before the DB write failed must be compensated.
+ verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testUpdatePolicyWithRuleResource_DeletesOldRulesAndAddsNew()
+ throws PolicyManagementException, RuleManagementException {
+
+ Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(
+ new PolicyResource(null, "ios", ResourceType.RULE, "old-rule", null)));
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing);
+
+ Rule created = mock(Rule.class);
+ when(created.getId()).thenReturn("new-rule");
+ when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created);
+
+ PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class));
+ Policy update = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(ruleRes));
+ when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(update);
+
+ policyManagementService.updatePolicy(update, TENANT_DOMAIN);
+
+ verify(ruleManagementService).addRule(any(Rule.class), eq(TENANT_DOMAIN));
+ verify(policyManagementDAO).updatePolicy(any(Policy.class), eq(TENANT_ID));
+ verify(ruleManagementService).deleteRule("old-rule", TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testUpdatePolicyWithRule_KeepsOldRulesWhenPersistenceFails()
+ throws PolicyManagementException, RuleManagementException {
+
+ Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(
+ new PolicyResource(null, "ios", ResourceType.RULE, "old-rule", null)));
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing);
+
+ Rule created = mock(Rule.class);
+ when(created.getId()).thenReturn("new-rule");
+ when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created);
+ when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID)))
+ .thenThrow(PolicyManagementServerException.class);
+
+ PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class));
+ Policy update = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(ruleRes));
+
+ try {
+ policyManagementService.updatePolicy(update, TENANT_DOMAIN);
+ Assert.fail("Expected PolicyManagementException");
+ } catch (PolicyManagementException expected) {
+ // Expected.
+ }
+
+ // The newly created rule must be compensated, but the old rule must survive the failed DB commit.
+ verify(ruleManagementService).deleteRule("new-rule", TENANT_DOMAIN);
+ verify(ruleManagementService, never()).deleteRule("old-rule", TENANT_DOMAIN);
+ }
+
+ @Test
+ public void testDeletePolicyWithRuleResource_RemovesRulesFromRuleMgt()
+ throws PolicyManagementException, RuleManagementException {
+
+ Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN,
+ Collections.singletonList(
+ new PolicyResource(null, "ios", ResourceType.RULE, "rule-1", null)));
+ when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing);
+
+ policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN);
+
+ verify(policyManagementDAO).deletePolicy(TEST_POLICY_ID, TENANT_ID);
+ verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN);
+ }
+}
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql
new file mode 100644
index 000000000000..73d754507af3
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+);
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml
new file mode 100644
index 000000000000..a5a1a6470cbc
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml
@@ -0,0 +1,686 @@
+
+
+
+
+
+
+
+ WSO2 Identity Server
+
+
+ IS
+
+
+ 5.3.0
+
+
+ localhost
+
+
+ localhost
+
+
+ local:/${carbon.context}/services/
+
+
+
+
+
+
+ IdentityServer
+
+
+
+
+
+
+ org.wso2.carbon
+
+
+ /
+
+
+
+
+
+
+
+
+ 15
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+ 9999
+
+ 11111
+
+
+
+
+
+ 10389
+
+ 8000
+
+
+
+
+
+ 10500
+
+
+
+
+
+
+
+
+ org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory
+
+
+
+
+
+
+
+
+ java
+
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+ 600
+
+
+
+ false
+
+
+
+
+
+
+
+ 30
+
+
+
+
+
+
+
+
+ 15
+
+
+
+
+
+ ${carbon.home}/repository/deployment/server/
+
+
+ 15
+
+
+ ${carbon.home}/repository/conf/axis2/axis2.xml
+
+
+ 30000
+
+
+ ${carbon.home}/repository/deployment/client/
+
+ ${carbon.home}/repository/conf/axis2/axis2_client.xml
+
+ true
+
+
+
+
+
+
+
+
+
+ admin
+ Default Administrator Role
+
+
+ user
+ Default User Role
+
+
+
+
+
+
+
+
+
+
+
+ ${carbon.home}/repository/resources/security/wso2carbon.jks
+
+ JKS
+
+ wso2carbon
+
+ wso2carbon
+
+ wso2carbon
+
+
+
+
+
+ ${carbon.home}/repository/resources/security/client-truststore.jks
+
+ JKS
+
+ wso2carbon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UserManager
+
+
+ false
+
+ org.wso2.carbon.identity.provider.AttributeCallbackHandler
+
+
+ org.wso2.carbon.identity.sts.store.DBTokenStore
+
+
+ true
+ allow
+
+
+
+
+
+
+ claim_mgt_menu
+ identity_mgt_emailtemplate_menu
+ identity_security_questions_menu
+
+
+
+ ${carbon.home}/tmp/work
+
+
+
+
+
+ true
+
+
+ 10
+
+
+ 30
+
+
+
+
+
+ 100
+
+
+
+ keystore
+ certificate
+ *
+
+ org.wso2.carbon.ui.transports.fileupload.AnyFileUploadExecutor
+
+
+
+
+ jarZip
+
+ org.wso2.carbon.ui.transports.fileupload.JarZipUploadExecutor
+
+
+
+ dbs
+
+ org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor
+
+
+
+ tools
+
+ org.wso2.carbon.ui.transports.fileupload.ToolsFileUploadExecutor
+
+
+
+ toolsAny
+
+ org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor
+
+
+
+
+
+
+
+
+
+ - info
+ org.wso2.carbon.core.transports.util.InfoProcessor
+
+
+ - wsdl
+ org.wso2.carbon.core.transports.util.Wsdl11Processor
+
+
+ - wsdl2
+ org.wso2.carbon.core.transports.util.Wsdl20Processor
+
+
+ - xsd
+ org.wso2.carbon.core.transports.util.XsdProcessor
+
+
+
+
+
+ false
+ false
+ true
+ svn
+ http://svnrepo.example.com/repos/
+ username
+ password
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${require.carbon.servlet}
+
+
+
+
+ true
+
+
+
+
+
+
+ default repository
+ http://product-dist.wso2.com/p2/carbon/releases/wilkes/
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml
new file mode 100644
index 000000000000..07de6831dbf4
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml
@@ -0,0 +1,743 @@
+
+
+
+
+
+
+
+
+ jdbc/WSO2IdentityDB
+
+
+
+
+ true
+ true
+ 0
+
+ true
+ 20160
+ 1140
+
+
+ true
+ 720
+
+
+
+
+
+
+ 15
+ 20160
+
+
+
+
+
+ ${carbon.home}/conf/keystores
+ SunX509
+ SunX509
+
+
+
+ SelfAndManaged
+ CertValidate
+
+
+
+
+
+
+
+
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/openidserver
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/openid
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/openid_login.do
+
+
+ false
+
+ 7200
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ -1
+ -1
+ -1
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/request-token
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/authorize-url
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/access-token
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/authorize
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/token
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/revoke
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/introspect
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/userinfo
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oidc/checksession
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oidc/logout
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_authz.do
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_error.do
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_consent.do
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_logout_consent.do
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_logout.do
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/.well-known/webfinger
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/identity/connect/register
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/jwks
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/oidcdiscovery
+
+
+ 300
+
+ 3600
+
+ 3600
+
+ 84600
+
+ 300
+
+ false
+
+ true
+
+ org.wso2.carbon.identity.oauth.tokenprocessor.PlainTextPersistenceProcessor
+
+
+
+ false
+
+
+
+
+
+ token
+ org.wso2.carbon.identity.oauth2.authz.handlers.AccessTokenResponseTypeHandler
+
+
+ code
+ org.wso2.carbon.identity.oauth2.authz.handlers.CodeResponseTypeHandler
+
+
+ id_token
+ org.wso2.carbon.identity.oauth2.authz.handlers.IDTokenResponseTypeHandler
+
+
+ id_token token
+ org.wso2.carbon.identity.oauth2.authz.handlers.IDTokenTokenResponseTypeHandler
+
+
+
+
+
+ authorization_code
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationCodeGrantHandler
+
+
+ password
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.PasswordGrantHandler
+
+
+ refresh_token
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler
+
+
+ client_credentials
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.ClientCredentialsGrantHandler
+
+
+ urn:ietf:params:oauth:grant-type:saml2-bearer
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.saml.SAML2BearerGrantHandler
+
+
+ iwa:ntlm
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.iwa.ntlm.NTLMAuthenticationGrantHandler
+
+
+ idTokenNotAllowedGrantType
+ org.wso2.carbon.identity.oauth2.token.handlers.grant.idTokenNotAllowedGrantHandler
+ false
+
+
+
+
+
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+ org.wso2.carbon.identity.oauth2.authcontext.JWTTokenGenerator
+ org.wso2.carbon.identity.oauth2.authcontext.DefaultClaimsRetriever
+ http://wso2.org/claims
+ SHA256withRSA
+ 10
+
+
+
+
+
+ org.wso2.carbon.identity.openidconnect.DefaultIDTokenBuilder
+ SHA256withRSA
+
+
+
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/token
+ org.wso2.carbon.identity.openidconnect.DefaultOIDCClaimsCallbackHandler
+ 3600
+ org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoUserStoreClaimRetriever
+ org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInforRequestDefaultValidator
+ org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoISAccessTokenValidator
+ org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoJSONResponseBuilder
+ false
+
+
+
+
+
+
+
+ gtalk
+ talk.google.com
+ 5222
+ gmail.com
+ multifactor1@gmail.com
+ wso2carbon
+
+
+
+
+
+ 157680000
+ 157680000
+ ${carbon.host}
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/samlsso
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/samlsso_logout.do
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/samlsso_notification.do
+ 5
+ 60000
+
+ false
+ http://wso2.org/claims
+ org.wso2.carbon.identity.sso.saml.builders.assertion.ExtendedDefaultAssertionBuilder
+
+ org.wso2.carbon.identity.sso.saml.builders.encryption.DefaultSSOEncrypter
+ org.wso2.carbon.identity.sso.saml.builders.signature.DefaultSSOSigner
+ org.wso2.carbon.identity.sso.saml.validators.SAML2HTTPRedirectDeflateSignatureValidator
+
+
+
+ 5
+ false
+ http://www.w3.org/2000/09/xmldsig#rsa-sha1
+ http://www.w3.org/2000/09/xmldsig#sha1
+ true
+
+
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/services/wso2carbon-sts
+
+
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/passivests
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/retry.do
+ org.wso2.carbon.identity.sts.passive.utils.NoPersistenceTokenStore
+ true
+
+
+
+
+ false
+ ${Ports.ThriftEntitlementReceivePort}
+ 10000
+
+ ${carbon.home}/repository/resources/security/wso2carbon.jks
+ wso2carbon
+
+
+ ${carbon.host}
+
+
+
+
+
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/wso2/scim/Users
+ ${carbon.protocol}://${carbon.host}:${carbon.management.port}/wso2/scim/Groups
+
+
+ 5
+
+
+ 10
+ local://services
+
+
+
+
+
+
+
+
+
+
+
+
+ org.wso2.carbon.identity.governance.store.JDBCIdentityDataStore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /permission/admin/manage/identity/identitymgt
+
+
+
+
+
+ /permission/admin/manage/identity/usermgt/view
+
+
+ /permission/admin/manage/identity/usermgt/view
+
+
+
+ /permission/admin/manage/identity/configmgt/list
+
+
+
+ /permission/admin/manage/identity/configmgt/add
+
+
+ /permission/admin/manage/identity/configmgt/update
+
+
+
+ /permission/admin/manage/identity/configmgt/delete
+
+
+
+ /permission/admin/manage/identity/configmgt/add
+
+
+ /permission/admin/manage/identity/configmgt/update
+
+
+
+ /permission/admin/manage/identity/configmgt/delete
+
+
+
+ /permission/admin/manage/identity/configmgt/add
+
+
+ /permission/admin/manage/identity/configmgt/update
+
+
+
+ /permission/admin/manage/identity/configmgt/delete
+
+
+
+
+
+
+ /permission/admin/manage/identity/consentmgt/add
+
+
+
+ /permission/admin/manage/identity/consentmgt/delete
+
+
+
+ /permission/admin/manage/identity/consentmgt/add
+
+
+
+ /permission/admin/manage/identity/consentmgt/delete
+
+
+
+ /permission/admin/manage/identity/consentmgt/add
+
+
+
+ /permission/admin/manage/identity/consentmgt/delete
+
+
+
+ /permission/admin/manage/identity/identitymgt
+
+
+
+ /permission/admin/manage/identity/applicationmgt/create
+
+
+ /permission/admin/manage/identity/applicationmgt/delete
+
+
+ /permission/admin/manage/identity/applicationmgt/update
+
+
+ /permission/admin/manage/identity/applicationmgt/view
+
+
+ /permission/admin/manage/identity/applicationmgt/delete
+
+
+ /permission/admin/manage/identity/applicationmgt/create
+
+
+ /permission/admin/manage/identity/applicationmgt/view
+
+
+ /permission/admin/manage/identity/pep
+
+
+ /permission/admin/manage/identity/usermgt/create
+
+
+ /permission/admin/manage/identity/usermgt/list
+
+
+ /permission/admin/manage/identity/rolemgt/create
+
+
+ /permission/admin/manage/identity/rolemgt/view
+
+
+ /permission/admin/manage/identity/usermgt/view
+
+
+ /permission/admin/manage/identity/usermgt/update
+
+
+ /permission/admin/manage/identity/usermgt/update
+
+
+ /permission/admin/manage/identity/usermgt/delete
+
+
+ /permission/admin/manage/identity/rolemgt/view
+
+
+ /permission/admin/manage/identity/rolemgt/update
+
+
+ /permission/admin/manage/identity/rolemgt/update
+
+
+ /permission/admin/manage/identity/rolemgt/delete
+
+
+ /permission/admin/login
+
+
+ /permission/admin/manage/identity/usermgt/delete
+
+
+ /permission/admin/login
+
+
+ /permission/admin/login
+
+
+ /permission/admin/manage/identity/usermgt/create
+
+
+
+
+
+
+
+
+ /permission/admin/manage/identity/usermgt
+
+
+ /permission/admin/manage/identity/applicationmgt
+
+
+
+
+
+
+ /permission/admin/manage/identity/usermgt/update
+
+
+
+
+
+ /permission/admin/manage/humantask/viewtasks
+
+
+ /permission/admin/login
+
+
+ /permission/admin/manage/identity/usermgt
+
+
+ /permission/admin/manage/identity/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /api/identity/user/v0.9
+ /api/identity/recovery/v0.9
+ /oauth2
+ /api/identity/entitlement
+
+
+ /identity/(.*)
+
+
+
+
+
+ applications,connections
+
+
+
+ 300
+
diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml
new file mode 100644
index 000000000000..cd69d0cb8866
--- /dev/null
+++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/policy-mgt/pom.xml b/components/policy-mgt/pom.xml
new file mode 100644
index 000000000000..42288b408e08
--- /dev/null
+++ b/components/policy-mgt/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ org.wso2.carbon.identity.framework
+ identity-framework
+ 7.11.135-SNAPSHOT
+ ../../pom.xml
+
+
+ 4.0.0
+ policy-mgt
+ pom
+ WSO2 Carbon - Policy Management Aggregator Module
+
+ This is a Carbon bundle that aggregates generic policy management modules.
+
+ http://wso2.org
+
+
+ org.wso2.carbon.identity.policy.management
+
+
+
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql
index e2d877c479fe..e3fc4e8a92cf 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql
@@ -3182,3 +3182,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID
/
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID)
/
+
+CREATE TABLE IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+)
+/
+CREATE TABLE IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+)
+/
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql
index 7bafed8fa168..d83c94d4a77a 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql
@@ -2109,3 +2109,22 @@ CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_K
-- WORKFLOWS --
CREATE INDEX IDX_WF_APPROVAL_STATE ON WF_WORKFLOW_APPROVAL_STATE(EVENT_ID,WORKFLOW_ID);
CREATE INDEX IDX_WF_APPROVAL_RELATION ON WF_WORKFLOW_APPROVAL_RELATION(TASK_ID, APPROVER_TYPE, APPROVER_NAME);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+);
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql
index 7484a815c6aa..67f87e6d6fc5 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql
@@ -2317,3 +2317,24 @@ GO
CREATE TRIGGER API_RESOURCE_DELETE_TRIGGER ON API_RESOURCE INSTEAD OF DELETE AS BEGIN DELETE FROM AUTHORIZED_API WHERE API_ID IN (SELECT ID FROM DELETED) DELETE FROM API_RESOURCE WHERE ID IN (SELECT ID FROM deleted) END;
GO
+
+IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_POLICY]') AND TYPE IN (N'U'))
+CREATE TABLE IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+);
+
+IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_POLICY_RESOURCE]') AND TYPE IN (N'U'))
+CREATE TABLE IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+);
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql
index 9e62af17c8e3..5433f2cd5ab1 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql
@@ -2324,3 +2324,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H
CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+)ENGINE NDB;
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+)ENGINE NDB;
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql
index b5aa3b4e5684..5e69c28eba74 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql
@@ -2136,3 +2136,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H
CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+)DEFAULT CHARACTER SET latin1 ENGINE INNODB;
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+)DEFAULT CHARACTER SET latin1 ENGINE INNODB;
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql
index 49771a8a6d69..db792a30de4b 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql
@@ -3334,3 +3334,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID
/
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID)
/
+
+CREATE TABLE IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+)
+/
+CREATE TABLE IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+)
+/
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql
index 89c6ecaff150..f92769ccf542 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql
@@ -3234,3 +3234,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID
/
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID)
/
+
+CREATE TABLE IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+)
+/
+CREATE TABLE IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+)
+/
diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql
index a68482e37fe2..caf771bb6a16 100644
--- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql
+++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql
@@ -2228,3 +2228,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H
CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE);
CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY (
+ ID CHAR(36) NOT NULL,
+ POLICY_NAME VARCHAR(255) NOT NULL,
+ TENANT_ID INTEGER NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_NAME, TENANT_ID)
+);
+
+CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE (
+ ID CHAR(36) NOT NULL,
+ POLICY_ID CHAR(36) NOT NULL,
+ TARGET VARCHAR(255) NOT NULL,
+ RESOURCE_TYPE VARCHAR(50) NOT NULL,
+ RESOURCE_ID CHAR(36) NOT NULL,
+ PRIMARY KEY (ID),
+ UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE),
+ FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE
+);
diff --git a/pom.xml b/pom.xml
index 1fe611983e85..d0cf760fc28a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
components/client-attestation-mgt
components/trusted-app-mgt
components/rule-mgt
+ components/policy-mgt
components/webhook-mgt
components/action-mgt
components/ai-services-mgt