This is an automated email from the ASF dual-hosted git repository.

vyommani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 787ad4b85 RANGER-5851: Admin: persist constraintDefs of service 
definitions (#1327)
787ad4b85 is described below

commit 787ad4b85b4ec9c1b66be355af98819a5e9d71fc
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Sun Oct 25 12:42:04 2026 +0530

    RANGER-5851: Admin: persist constraintDefs of service definitions (#1327)
    
    * RANGER-5851: Admin: persist constraintDefs of service definitions
    
    * RANGER-5851: Admin: persist constraintDefs of service definitions
    
    * RANGER-5851: Admin: persist constraintDefs of service definitions
    
    * RANGER-5851: incorporated review comments
---
 .../ranger/plugin/errors/ValidationErrorCode.java  |   7 +
 .../validation/RangerServiceDefValidator.java      | 270 ++++++++++++++++++++-
 .../ranger/plugin/model/TestRangerServiceDef.java  |   8 +-
 .../validation/TestRangerServiceDefValidator.java  | 166 +++++++++++++
 dev-support/checkstyle-suppressions.xml            |   1 +
 .../optimized/current/ranger_core_db_mysql.sql     |   3 +
 .../patches/079-add-def_text-to-x_service_def.sql  |  30 +++
 .../optimized/current/ranger_core_db_oracle.sql    |   3 +
 .../patches/079-add-def_text-to-x_service_def.sql  |  24 ++
 .../optimized/current/ranger_core_db_postgres.sql  |   3 +
 .../patches/079-add-def_text-to-x_service_def.sql  |  32 +++
 .../current/ranger_core_db_sqlanywhere.sql         |   3 +
 .../patches/079-add-def_text-to-x_service_def.sql  |  20 ++
 .../optimized/current/ranger_core_db_sqlserver.sql |   3 +
 .../patches/079-add-def_text-to-x_service_def.sql  |  22 ++
 .../java/org/apache/ranger/biz/ServiceDBStore.java |  18 ++
 .../org/apache/ranger/entity/XXServiceDefBase.java |  27 +++
 .../patch/PatchForServiceDefJson_J10067.java       | 134 ++++++++++
 .../service/RangerServiceDefServiceBase.java       |  73 +++++-
 .../org/apache/ranger/biz/TestServiceDBStore.java  |  38 ++-
 .../patch/TestPatchForServiceDefJson_J10067.java   | 113 +++++++++
 .../service/TestRangerServiceDefService.java       | 201 +++++++++++++++
 .../service/TestRangerServiceDefServiceBase.java   |  93 +++++++
 23 files changed, 1273 insertions(+), 19 deletions(-)

diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/errors/ValidationErrorCode.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/errors/ValidationErrorCode.java
index 83cae22d8..976dc7944 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/errors/ValidationErrorCode.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/errors/ValidationErrorCode.java
@@ -67,6 +67,13 @@ public enum ValidationErrorCode {
     SERVICE_DEF_VALIDATION_ERR_INVALID_SERVICE_RESOURCE_LEVELS(2021, 
"Resource-def levels are not in increasing order in an hierarchy"),
     SERVICE_DEF_VALIDATION_ERR_NOT_LOWERCASE_NAME(2022, "{0}:[{1}] Invalid 
resource name. Resource name should consist of only lowercase, hyphen or 
underscore characters"),
     
SERVICE_DEF_VALIDATION_ERR_INVALID_MANADORY_VALUE_FOR_SERVICE_RESOURCE(2023, 
"{0} cannot be mandatory because {1}(parent) is not mandatory"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_RESOURCE(2026, 
"resource[{0}] of constraint def[{1}] is not defined on the service def"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_MISSING_ANCESTOR(2027, "constraint 
def[{0}] lists resource[{1}] without ancestor[{2}]"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_ACCESS_TYPE(2028, "access 
type[{0}] of constraint def[{1}] is not defined on the service def"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_MISSING_TYPE(2029, "type of 
constraint def[{0}] parameter[{1}] was null/empty"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_INVALID_TYPE(2030, "type[{0}] 
of constraint def[{1}] parameter[{2}] is not among valid types: {3}"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_UNKNOWN_ENUM(2031, 
"subtype[{0}] of constraint def[{1}] parameter[{2}] was not among defined 
enums[{3}]"),
+    SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_SUBTYPE_NOT_EMPTY(2032, 
"subtype[{0}] of constraint def[{1}] parameter[{2}] must be empty when type is 
[{3}]"),
 
     // POLICY VALIDATION
     POLICY_VALIDATION_ERR_UNSUPPORTED_ACTION(3001, "Internal error: method 
signature isValid(Long) is only supported for DELETE"),
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java
index 1f0c99d29..45725b476 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java
@@ -27,6 +27,8 @@
 import org.apache.ranger.plugin.model.RangerPolicy;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
+import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintParamDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskTypeDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef;
@@ -50,7 +52,8 @@
 public class RangerServiceDefValidator extends RangerValidator {
     private static final Logger LOG = 
LoggerFactory.getLogger(RangerServiceDefValidator.class);
 
-    private static final Set<String> VALID_CONFIG_TYPES = 
ImmutableSet.of("bool", "enum", "int", "string", "password", "path");
+    private static final Set<String> VALID_CONFIG_TYPES           = 
ImmutableSet.of("bool", "enum", "int", "string", "password", "path");
+    private static final Set<String> VALID_CONSTRAINT_PARAM_TYPES = 
ImmutableSet.of("string", "int", "bool", "enum");
 
     public RangerServiceDefValidator(ServiceStore store) {
         super(store);
@@ -220,6 +223,7 @@ boolean isValid(final RangerServiceDef serviceDef, final 
Action action, final Li
             }
 
             valid = isValidPolicyConditions(serviceDef.getId(), 
serviceDef.getPolicyConditions(), failures, action) && valid;
+            valid = isValidConstraintDefs(serviceDef, failures) && valid;
             valid = isValidDataMaskTypes(serviceDef.getId(), 
serviceDef.getDataMaskDef().getMaskTypes(), failures, action) && valid;
         }
 
@@ -532,6 +536,270 @@ boolean isValidPolicyConditions(Long serviceDefId, 
List<RangerPolicyConditionDef
         return valid;
     }
 
+    boolean isValidConstraintDefs(RangerServiceDef serviceDef, 
List<ValidationFailureDetails> failures) {
+        LOG.debug("==> RangerServiceDefValidator.isValidConstraintDefs({}, 
{})", serviceDef, failures);
+
+        boolean valid = true;
+
+        if (serviceDef == null || 
CollectionUtils.isEmpty(serviceDef.getConstraintDefs())) {
+            LOG.debug("constraint def collection was null/empty. Ok.");
+        } else {
+            Map<String, RangerResourceDef> resourceDefs   = 
resourceDefMap(serviceDef.getResources());
+            Set<String>                    accessTypes    = 
accessTypeNames(serviceDef.getAccessTypes());
+            Map<String, RangerEnumDef>     enumDefs       = 
getEnumDefMap(serviceDef.getEnums());
+            Set<Long>                      ids            = new HashSet<>();
+            Set<String>                    names          = new HashSet<>();
+
+            for (RangerConstraintDef constraintDef : 
serviceDef.getConstraintDefs()) {
+                if (constraintDef == null) {
+                    failures.add(new ValidationFailureDetailsBuilder()
+                            .field("constraint def")
+                            .isMissing()
+                            .becauseOf("constraint def was null")
+                            .build());
+
+                    valid = false;
+
+                    continue;
+                }
+
+                String constraintName = constraintDef.getName();
+
+                valid = isUnique(constraintDef.getItemId(), ids, "constraint 
def itemId", "constraint defs", failures) && valid;
+                valid = isUnique(constraintName, names, "constraint def name", 
"constraint defs", failures) && valid;
+                valid = isValidConstraintResources(constraintDef, 
resourceDefs, failures) && valid;
+                valid = isValidConstraintAccessTypes(constraintDef, 
accessTypes, failures) && valid;
+                valid = isValidConstraintParams(constraintDef, enumDefs, 
failures) && valid;
+            }
+        }
+
+        LOG.debug("<== RangerServiceDefValidator.isValidConstraintDefs({}, 
{}): {}", serviceDef, failures, valid);
+
+        return valid;
+    }
+
+    private boolean isValidConstraintResources(RangerConstraintDef 
constraintDef, Map<String, RangerResourceDef> resourceDefs, 
List<ValidationFailureDetails> failures) {
+        boolean      valid          = true;
+        List<String> resources      = constraintDef.getResources();
+        String       constraintName = constraintDef.getName();
+
+        if (CollectionUtils.isEmpty(resources)) {
+            return true;
+        }
+
+        Set<String> listed = new HashSet<>();
+
+        for (String resourceName : resources) {
+            if (StringUtils.isNotBlank(resourceName)) {
+                listed.add(resourceName);
+            }
+        }
+
+        for (String resourceName : resources) {
+            if (StringUtils.isBlank(resourceName) || 
!resourceDefs.containsKey(resourceName)) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_RESOURCE;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def resource")
+                        .subField(StringUtils.isBlank(resourceName) ? 
constraintName : resourceName)
+                        .isSemanticallyIncorrect()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(resourceName, 
constraintName))
+                        .build());
+
+                valid = false;
+
+                continue;
+            }
+
+            if (!hasConstraintAncestors(resourceName, listed, resourceDefs, 
constraintName, failures)) {
+                valid = false;
+            }
+        }
+
+        return valid;
+    }
+
+    private boolean hasConstraintAncestors(String resourceName, Set<String> 
listed, Map<String, RangerResourceDef> resourceDefs, String constraintName, 
List<ValidationFailureDetails> failures) {
+        Set<String>       seen        = new HashSet<>();
+        RangerResourceDef resourceDef = resourceDefs.get(resourceName);
+        String            parent      = resourceDef == null ? null : 
resourceDef.getParent();
+
+        seen.add(resourceName);
+
+        while (StringUtils.isNotBlank(parent)) {
+            if (!seen.add(parent)) {
+                break;
+            }
+
+            if (!listed.contains(parent)) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_MISSING_ANCESTOR;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def ancestor")
+                        .subField(resourceName)
+                        .isSemanticallyIncorrect()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(constraintName, 
resourceName, parent))
+                        .build());
+
+                return false;
+            }
+
+            RangerResourceDef parentDef = resourceDefs.get(parent);
+
+            parent = parentDef == null ? null : parentDef.getParent();
+        }
+
+        return true;
+    }
+
+    private boolean isValidConstraintAccessTypes(RangerConstraintDef 
constraintDef, Set<String> accessTypes, List<ValidationFailureDetails> 
failures) {
+        boolean      valid                  = true;
+        List<String> constraintAccessTypes = constraintDef.getAccessTypes();
+
+        if (CollectionUtils.isEmpty(constraintAccessTypes)) {
+            return true;
+        }
+
+        String constraintName = constraintDef.getName();
+
+        for (String accessType : constraintAccessTypes) {
+            if (StringUtils.isBlank(accessType) || 
!accessTypes.contains(accessType)) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_ACCESS_TYPE;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def access type")
+                        .subField(StringUtils.isBlank(accessType) ? 
constraintName : accessType)
+                        .isSemanticallyIncorrect()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(accessType, 
constraintName))
+                        .build());
+
+                valid = false;
+            }
+        }
+
+        return valid;
+    }
+
+    private boolean isValidConstraintParams(RangerConstraintDef constraintDef, 
Map<String, RangerEnumDef> enumDefs, List<ValidationFailureDetails> failures) {
+        boolean                        valid          = true;
+        List<RangerConstraintParamDef> parameters     = 
constraintDef.getParameters();
+        String                         constraintName = 
constraintDef.getName();
+
+        if (CollectionUtils.isEmpty(parameters)) {
+            return true;
+        }
+
+        Set<String> paramNames = new HashSet<>();
+
+        for (RangerConstraintParamDef parameter : parameters) {
+            if (parameter == null) {
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def parameter")
+                        .subField(constraintName)
+                        .isMissing()
+                        .becauseOf("constraint def parameter was null")
+                        .build());
+
+                valid = false;
+
+                continue;
+            }
+
+            String paramName = parameter.getName();
+
+            valid = isUnique(paramName, constraintName, paramNames, 
"constraint def parameter name", "constraint def parameters", failures) && 
valid;
+
+            String type = parameter.getType();
+
+            if (StringUtils.isBlank(type)) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_MISSING_TYPE;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def parameter type")
+                        .subField(paramName)
+                        .isMissing()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(constraintName, paramName))
+                        .build());
+
+                valid = false;
+            } else if (!VALID_CONSTRAINT_PARAM_TYPES.contains(type)) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_INVALID_TYPE;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def parameter type")
+                        .subField(paramName)
+                        .isSemanticallyIncorrect()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(type, constraintName, 
paramName, VALID_CONSTRAINT_PARAM_TYPES))
+                        .build());
+
+                valid = false;
+            } else if ("enum".equals(type)) {
+                String subType = parameter.getSubType();
+
+                if (StringUtils.isBlank(subType) || 
!enumDefs.containsKey(subType)) {
+                    ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_UNKNOWN_ENUM;
+
+                    failures.add(new ValidationFailureDetailsBuilder()
+                            .field("constraint def parameter subtype")
+                            .subField(paramName)
+                            .isSemanticallyIncorrect()
+                            .errorCode(error.getErrorCode())
+                            .becauseOf(error.getMessage(subType, 
constraintName, paramName, enumDefs.keySet()))
+                            .build());
+
+                    valid = false;
+                }
+            } else if (StringUtils.isNotBlank(parameter.getSubType())) {
+                ValidationErrorCode error = 
ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_SUBTYPE_NOT_EMPTY;
+
+                failures.add(new ValidationFailureDetailsBuilder()
+                        .field("constraint def parameter subtype")
+                        .subField(paramName)
+                        .isSemanticallyIncorrect()
+                        .errorCode(error.getErrorCode())
+                        .becauseOf(error.getMessage(parameter.getSubType(), 
constraintName, paramName, type))
+                        .build());
+
+                valid = false;
+            }
+        }
+
+        return valid;
+    }
+
+    private Map<String, RangerResourceDef> 
resourceDefMap(List<RangerResourceDef> resources) {
+        Map<String, RangerResourceDef> resourceDefs = new HashMap<>();
+
+        if (resources != null) {
+            for (RangerResourceDef resourceDef : resources) {
+                if (resourceDef != null && 
StringUtils.isNotBlank(resourceDef.getName())) {
+                    resourceDefs.put(resourceDef.getName(), resourceDef);
+                }
+            }
+        }
+
+        return resourceDefs;
+    }
+
+    private Set<String> accessTypeNames(List<RangerAccessTypeDef> 
accessTypeDefs) {
+        Set<String> accessTypes = new HashSet<>();
+
+        if (accessTypeDefs != null) {
+            for (RangerAccessTypeDef accessTypeDef : accessTypeDefs) {
+                if (accessTypeDef != null && 
StringUtils.isNotBlank(accessTypeDef.getName())) {
+                    accessTypes.add(accessTypeDef.getName());
+                }
+            }
+        }
+
+        return accessTypes;
+    }
+
     boolean isValidConfigs(List<RangerServiceConfigDef> configs, 
List<RangerEnumDef> enumDefs, List<ValidationFailureDetails> failures) {
         LOG.debug("==> RangerServiceDefValidator.isValidConfigs({}, {}, {})", 
configs, enumDefs, failures);
 
diff --git 
a/agents-common/src/test/java/org/apache/ranger/plugin/model/TestRangerServiceDef.java
 
b/agents-common/src/test/java/org/apache/ranger/plugin/model/TestRangerServiceDef.java
index c36eb6232..1cb4438a8 100644
--- 
a/agents-common/src/test/java/org/apache/ranger/plugin/model/TestRangerServiceDef.java
+++ 
b/agents-common/src/test/java/org/apache/ranger/plugin/model/TestRangerServiceDef.java
@@ -365,7 +365,7 @@ public void test14_ConstraintDefsJsonRoundTrip() throws 
Exception {
         param.setName("approver");
         param.setLabel("Approver");
         param.setDescription("User who must approve");
-        param.setType("user");
+        param.setType("string");
         param.setSubType(null);
         Assertions.assertNull(param.getSubType());
         param.setMandatory(null);
@@ -472,7 +472,7 @@ public void test14_ConstraintDefsJsonRoundTrip() throws 
Exception {
                 + "\"name\":\"approver\","
                 + "\"label\":\"Approver\","
                 + "\"description\":\"User who must approve\","
-                + "\"type\":\"user\","
+                + "\"type\":\"string\","
                 + "\"mandatory\":true,"
                 + "\"defaultValue\":\"alice\","
                 + "\"rbKeyLabel\":\"rb.approver\","
@@ -496,13 +496,13 @@ public void test14_ConstraintDefsJsonRoundTrip() throws 
Exception {
         Map<String, String> tbl = new HashMap<>();
         String canonicalResource = StringUtil.dedupString(new String("tool"), 
tbl);
         String canonicalName = StringUtil.dedupString(new 
String("requireApproval"), tbl);
-        String canonicalType    = StringUtil.dedupString(new String("user"), 
tbl);
+        String canonicalType    = StringUtil.dedupString(new String("string"), 
tbl);
         String canonicalSubType = StringUtil.dedupString(new 
String("MaskTypes"), tbl);
         parsedDef.setName(new String("requireApproval"));
         List<String> resources = new ArrayList<>();
         resources.add(new String("tool"));
         parsedDef.setResources(resources);
-        parsedDef.getParameters().get(0).setType(new String("user"));
+        parsedDef.getParameters().get(0).setType(new String("string"));
         parsedDef.getParameters().get(0).setSubType(new String("MaskTypes"));
         parsedDef.dedupStrings(tbl);
         Assertions.assertSame(canonicalName, parsedDef.getName());
diff --git 
a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java
 
b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java
index a178f3174..d832e01ed 100644
--- 
a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java
+++ 
b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java
@@ -19,8 +19,11 @@
 
 package org.apache.ranger.plugin.model.validation;
 
+import org.apache.ranger.plugin.errors.ValidationErrorCode;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
+import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintParamDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
@@ -32,11 +35,14 @@
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -662,4 +668,164 @@ public final void test_isValidConfigs_enumTypeBranches() {
         assertFalse(validator.isValidConfigs(configs, enumDefs, failures));
         utils.checkFailureForSemanticError(failures, "config def subtype", 
"enumCfg");
     }
+
+    @Test
+    public final void test_isValidConstraintDefs() {
+        assertTrue(validator.isValidConstraintDefs(new RangerServiceDef(), 
failures));
+        assertTrue(failures.isEmpty());
+
+        RangerServiceDef def = constraintServiceDef();
+        RangerConstraintDef valid = constraintDef(1L, "requireApproval", 
Arrays.asList("database", "udf"), Arrays.asList("execute"));
+        List<RangerConstraintParamDef> validParams = new ArrayList<>();
+        validParams.add(param("approver", "string", null, true));
+        validParams.add(param("rowCount", "int", null, false));
+        validParams.add(param("enabled", "bool", null, false));
+        validParams.add(param("mode", "enum", "authType", false));
+        valid.setParameters(validParams);
+        def.setConstraintDefs(Collections.singletonList(valid));
+        assertTrue(validator.isValidConstraintDefs(def, failures));
+        assertTrue(failures.isEmpty());
+
+        RangerConstraintDef emptyResources = constraintDef(1L, 
"requireApproval", new ArrayList<>(), Arrays.asList("execute"));
+        
emptyResources.setParameters(Collections.singletonList(param("approver", 
"string", null, true)));
+        def.setConstraintDefs(Collections.singletonList(emptyResources));
+        assertTrue(validator.isValidConstraintDefs(def, failures));
+        assertTrue(failures.isEmpty());
+
+        assertFalse(validator.isValidConfigType("user", "approver", failures));
+        utils.checkFailureForSemanticError(failures, "config def type", 
"approver");
+        failures.clear();
+        assertFalse(validator.isValidConfigType("group", "approverGroup", 
failures));
+        utils.checkFailureForSemanticError(failures, "config def type", 
"approverGroup");
+        failures.clear();
+        assertFalse(validator.isValidConfigType("role", "approverRole", 
failures));
+        utils.checkFailureForSemanticError(failures, "config def type", 
"approverRole");
+    }
+
+    @Test
+    public final void test_isValid_rejectsInvalidConstraintDef() {
+        RangerServiceDef def = constraintServiceDef();
+        def.setName("agent");
+        def.setDisplayName("Agent");
+        def.setConstraintDefs(Collections.singletonList(constraintDef(1L, 
"requireApproval", Arrays.asList("missing"), Arrays.asList("select"))));
+
+        assertFalse(validator.isValid(def, Action.CREATE, failures));
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_RESOURCE.getErrorCode());
+    }
+
+    @Test
+    public final void test_isValidConstraintDefs_failures() {
+        RangerServiceDef def = constraintServiceDef();
+        List<RangerConstraintDef> constraintDefs = new ArrayList<>();
+        constraintDefs.add(null);
+
+        RangerConstraintDef missingAncestor = constraintDef(1L, "maxRows", 
Arrays.asList("udf"), Arrays.asList("select"));
+        
missingAncestor.setParameters(Collections.singletonList(param("rowCount", 
"int", null, true)));
+        constraintDefs.add(missingAncestor);
+
+        RangerConstraintDef invalid = constraintDef(1L, " ", 
Arrays.asList("missing", ""), Arrays.asList("drop", " "));
+        List<RangerConstraintParamDef> params = new ArrayList<>();
+        params.add(param("rowCount", "password", null, true));
+        params.add(param("rowCount", "int", null, false));
+        params.add(null);
+        params.add(param("mode", "enum", "no-such-enum", false));
+        params.add(param("kind", "enum", " ", false));
+        params.add(param("approver", "string", "authType", true));
+        params.add(param("approverUser", "user", null, false));
+        params.add(param("approverGroup", "group", null, false));
+        params.add(param("approverRole", "role", null, false));
+        params.add(param("limit", null, null, false));
+        invalid.setParameters(params);
+        constraintDefs.add(invalid);
+
+        RangerConstraintDef duplicateName = constraintDef(2L, "maxRows", 
Arrays.asList("database", "table"), Arrays.asList("select"));
+        
duplicateName.setParameters(Collections.singletonList(param("rowCount", "int", 
null, true)));
+        constraintDefs.add(duplicateName);
+
+        def.setConstraintDefs(constraintDefs);
+        assertFalse(validator.isValidConstraintDefs(def, failures));
+        utils.checkFailureForMissingValue(failures, "constraint def");
+        utils.checkFailureForMissingValue(failures, "constraint def name");
+        utils.checkFailureForSemanticError(failures, "constraint def itemId", 
"1");
+        utils.checkFailureForSemanticError(failures, "constraint def name", 
"maxRows");
+        utils.checkFailureForSemanticError(failures, "constraint def 
ancestor", "udf");
+        utils.checkFailureForSemanticError(failures, "constraint def 
resource", "missing");
+        utils.checkFailureForSemanticError(failures, "constraint def access 
type", "drop");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
type", "rowCount");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
type", "approverUser");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
type", "approverGroup");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
type", "approverRole");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
name", "rowCount");
+        utils.checkFailureForMissingValue(failures, "constraint def 
parameter");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
subtype", "mode");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
subtype", "kind");
+        utils.checkFailureForSemanticError(failures, "constraint def parameter 
subtype", "approver");
+        utils.checkFailureForMissingValue(failures, "constraint def parameter 
type", "limit");
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_RESOURCE.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_MISSING_ANCESTOR.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_UNKNOWN_ACCESS_TYPE.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_MISSING_TYPE.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_INVALID_TYPE.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_UNKNOWN_ENUM.getErrorCode());
+        
assertHasErrorCode(ValidationErrorCode.SERVICE_DEF_VALIDATION_ERR_CONSTRAINT_PARAM_SUBTYPE_NOT_EMPTY.getErrorCode());
+    }
+
+    private void assertHasErrorCode(int errorCode) {
+        for (ValidationFailureDetails failure : failures) {
+            if (failure.errorCode == errorCode) {
+                return;
+            }
+        }
+
+        fail("expected error code " + errorCode + " in " + failures);
+    }
+
+    private RangerServiceDef constraintServiceDef() {
+        RangerServiceDef def = new RangerServiceDef();
+        def.setResources(utils.createResourceDefs(new Object[][] {
+                {"database", Boolean.FALSE, Boolean.FALSE, Boolean.TRUE, null, 
null, 10},
+                {"table", Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null, 
"database", 20},
+                {"udf", Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null, 
"database", 20},
+                {"column", Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, null, 
"table", 30}
+        }));
+
+        RangerAccessTypeDef select = new RangerAccessTypeDef();
+        select.setName("select");
+        RangerAccessTypeDef execute = new RangerAccessTypeDef();
+        execute.setName("execute");
+        List<RangerAccessTypeDef> accessTypes = new ArrayList<>();
+        accessTypes.add(select);
+        accessTypes.add(execute);
+        def.setAccessTypes(accessTypes);
+
+        RangerEnumDef enumDef = new RangerEnumDef();
+        enumDef.setName("authType");
+        List<RangerEnumDef> enums = new ArrayList<>();
+        enums.add(enumDef);
+        def.setEnums(enums);
+
+        return def;
+    }
+
+    private RangerConstraintDef constraintDef(Long itemId, String name, 
List<String> resources, List<String> accessTypes) {
+        RangerConstraintDef constraintDef = new RangerConstraintDef();
+
+        constraintDef.setItemId(itemId);
+        constraintDef.setName(name);
+        constraintDef.setResources(resources);
+        constraintDef.setAccessTypes(accessTypes);
+
+        return constraintDef;
+    }
+
+    private RangerConstraintParamDef param(String name, String type, String 
subType, boolean mandatory) {
+        RangerConstraintParamDef paramDef = new RangerConstraintParamDef();
+
+        paramDef.setName(name);
+        paramDef.setType(type);
+        paramDef.setSubType(subType);
+        paramDef.setMandatory(mandatory);
+
+        return paramDef;
+    }
 }
diff --git a/dev-support/checkstyle-suppressions.xml 
b/dev-support/checkstyle-suppressions.xml
index f20f609e9..b82da722a 100644
--- a/dev-support/checkstyle-suppressions.xml
+++ b/dev-support/checkstyle-suppressions.xml
@@ -93,4 +93,5 @@
   <suppress files="PatchForAtlasPolicyUpdateForEntityRead_J10064" 
checks="TypeName"/>
   <suppress files="PatchForOzoneServiceDefPolicyConditionUpdate_J10066" 
checks="TypeName"/>
   <suppress files="PatchForOzoneServiceDefAssumeRoleUpdate_J10065" 
checks="TypeName"/>
+  <suppress files="PatchForServiceDefJson_J10067" checks="TypeName"/>
 </suppressions>
diff --git a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql 
b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
index 8ecc59f4a..0818c8590 100644
--- a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
+++ b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
@@ -533,6 +533,7 @@ CREATE TABLE `x_service_def` (
 `rb_key_description` varchar(1024) DEFAULT NULL,
 `is_enabled` tinyint DEFAULT 1,
 `def_options` VARCHAR(1024) DEFAULT NULL NULL,
+`def_text` MEDIUMTEXT NULL DEFAULT NULL,
 primary key (`id`),
 KEY `x_service_def_added_by_id` (`added_by_id`),
 KEY `x_service_def_upd_by_id` (`upd_by_id`),
@@ -1975,6 +1976,7 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('076',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('077',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('078',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('079',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('DB_PATCHES',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
 
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10001',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
@@ -2029,4 +2031,5 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10064',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10065',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10066',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10067',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('JAVA_PATCHES',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
diff --git 
a/security-admin/db/mysql/patches/079-add-def_text-to-x_service_def.sql 
b/security-admin/db/mysql/patches/079-add-def_text-to-x_service_def.sql
new file mode 100644
index 000000000..7acdb8905
--- /dev/null
+++ b/security-admin/db/mysql/patches/079-add-def_text-to-x_service_def.sql
@@ -0,0 +1,30 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF 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.
+
+drop procedure if exists add_def_text_in_x_service_def;
+
+delimiter ;;
+create procedure add_def_text_in_x_service_def() begin
+
+if not exists (select * from information_schema.columns where 
table_schema=database() and table_name = 'x_service_def' and 
column_name='def_text') then
+        ALTER TABLE x_service_def ADD def_text MEDIUMTEXT NULL DEFAULT NULL;
+end if;
+
+end;;
+
+delimiter ;
+call add_def_text_in_x_service_def();
+
+drop procedure if exists add_def_text_in_x_service_def;
diff --git 
a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql 
b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
index ddca10bb5..ec8dfff61 100644
--- a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
+++ b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
@@ -662,6 +662,7 @@ rb_key_label VARCHAR(1024) DEFAULT NULL NULL,
 rb_key_description VARCHAR(1024) DEFAULT NULL NULL,
 is_enabled NUMBER(1) DEFAULT '1' NULL,
 def_options VARCHAR(1024) DEFAULT NULL NULL,
+def_text CLOB DEFAULT NULL NULL,
 PRIMARY KEY (id),
 CONSTRAINT x_service_def_FK_added_by_id FOREIGN KEY (added_by_id) REFERENCES 
x_portal_user (id),
 CONSTRAINT x_service_def_FK_upd_by_id FOREIGN KEY (upd_by_id) REFERENCES 
x_portal_user (id)
@@ -2164,6 +2165,7 @@ INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,act
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval, '076',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval, '077',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval, '078',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
+INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval, '079',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval, 'DB_PATCHES',sys_extract_utc(systimestamp),'Ranger 
1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 
 INSERT INTO x_user_module_perm 
(id,user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) 
VALUES 
(X_USER_MODULE_PERM_SEQ.nextval,getXportalUIdByLoginId('admin'),getModulesIdByName('Reports'),sys_extract_utc(systimestamp),sys_extract_utc(systimestamp),getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
@@ -2253,5 +2255,6 @@ INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,act
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval,'J10064',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval,'J10065',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval,'J10066',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
+INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval,'J10067',sys_extract_utc(systimestamp),'Ranger 
3.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h 
(id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
(X_DB_VERSION_H_SEQ.nextval,'JAVA_PATCHES',sys_extract_utc(systimestamp),'Ranger
 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 commit;
diff --git 
a/security-admin/db/oracle/patches/079-add-def_text-to-x_service_def.sql 
b/security-admin/db/oracle/patches/079-add-def_text-to-x_service_def.sql
new file mode 100644
index 000000000..70d084fca
--- /dev/null
+++ b/security-admin/db/oracle/patches/079-add-def_text-to-x_service_def.sql
@@ -0,0 +1,24 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF 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.
+
+DECLARE
+        v_count number:=0;
+BEGIN
+        select count(*) into v_count from user_tab_cols where 
table_name='X_SERVICE_DEF' and column_name='DEF_TEXT';
+        if (v_count = 0) then
+                execute immediate 'ALTER TABLE x_service_def ADD def_text CLOB 
DEFAULT NULL NULL';
+        end if;
+        commit;
+END;/
diff --git 
a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql 
b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
index 654d19aa7..019ab5782 100644
--- a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
+++ b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
@@ -549,6 +549,7 @@ rb_key_label VARCHAR(1024) DEFAULT NULL NULL,
 rb_key_description VARCHAR(1024) DEFAULT NULL NULL,
 is_enabled BOOLEAN DEFAULT '1' NULL,
 def_options VARCHAR(1024) DEFAULT NULL NULL,
+def_text TEXT DEFAULT NULL NULL,
 PRIMARY KEY(id),
 CONSTRAINT x_service_def_FK_added_by_id FOREIGN KEY(added_by_id) REFERENCES 
x_portal_user(id),
 CONSTRAINT x_service_def_FK_upd_by_id FOREIGN KEY(upd_by_id) REFERENCES 
x_portal_user(id)
@@ -2078,6 +2079,7 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('076',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('077',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('078',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('079',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('DB_PATCHES',current_timestamp,'Ranger 
1.0.0',current_timestamp,'localhost','Y');
 
 INSERT INTO x_user_module_perm 
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) 
VALUES
@@ -2186,6 +2188,7 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10064',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10065',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10066',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10067',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('JAVA_PATCHES',current_timestamp,'Ranger 
1.0.0',current_timestamp,'localhost','Y');
 
 DROP VIEW IF EXISTS vx_principal;
diff --git 
a/security-admin/db/postgres/patches/079-add-def_text-to-x_service_def.sql 
b/security-admin/db/postgres/patches/079-add-def_text-to-x_service_def.sql
new file mode 100644
index 000000000..30e74e03e
--- /dev/null
+++ b/security-admin/db/postgres/patches/079-add-def_text-to-x_service_def.sql
@@ -0,0 +1,32 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF 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.
+
+select 'delimiter start';
+CREATE OR REPLACE FUNCTION add_def_text_in_x_service_def()
+RETURNS void AS $$
+DECLARE
+ v_column_exists integer := 0;
+BEGIN
+ select count(*) into v_column_exists from pg_attribute where attrelid 
in(select oid from pg_class where relname='x_service_def') and 
attname='def_text';
+ IF v_column_exists = 0 THEN
+  ALTER TABLE x_service_def ADD COLUMN def_text TEXT DEFAULT NULL NULL;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+select 'delimiter end';
+
+select add_def_text_in_x_service_def();
+select 'delimiter end';
+commit;
diff --git 
a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
 
b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
index 678fe343b..cac26327a 100644
--- 
a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
+++ 
b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
@@ -497,6 +497,7 @@ create table dbo.x_service_def(
        rb_key_description varchar(1024) DEFAULT NULL NULL,
        is_enabled tinyint DEFAULT 1 NULL,
        def_options varchar(1024) DEFAULT NULL NULL,
+       def_text text DEFAULT NULL NULL,
        CONSTRAINT x_service_def_PK_id PRIMARY KEY CLUSTERED(id)
 )
 GO
@@ -2256,6 +2257,7 @@ GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('077',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('078',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('079',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('DB_PATCHES',CURRENT_TIMESTAMP,'Ranger 
1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
@@ -2420,6 +2422,7 @@ GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10065',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10066',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10067',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('JAVA_PATCHES',CURRENT_TIMESTAMP,'Ranger 
1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
diff --git 
a/security-admin/db/sqlanywhere/patches/079-add-def_text-to-x_service_def.sql 
b/security-admin/db/sqlanywhere/patches/079-add-def_text-to-x_service_def.sql
new file mode 100644
index 000000000..a50014bb5
--- /dev/null
+++ 
b/security-admin/db/sqlanywhere/patches/079-add-def_text-to-x_service_def.sql
@@ -0,0 +1,20 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF 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.
+
+IF NOT EXISTS(select * from SYS.SYSCOLUMNS where tname = 'x_service_def' and 
cname = 'def_text') THEN
+               ALTER TABLE dbo.x_service_def ADD def_text text DEFAULT NULL 
NULL;
+END IF;
+GO
+exit
diff --git 
a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql 
b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
index 68c864e74..fef115ed7 100644
--- a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
+++ b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
@@ -1497,6 +1497,7 @@ CREATE TABLE [dbo].[x_service_def](
         [rb_key_description] [varchar](1024) DEFAULT NULL NULL,
         [is_enabled] [tinyint] DEFAULT 1 NULL,
         [def_options] [varchar](1024) DEFAULT NULL NULL,
+        [def_text] [nvarchar](max) DEFAULT NULL NULL,
         PRIMARY KEY CLUSTERED
 (
         [id] ASC
@@ -4538,6 +4539,7 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('076',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('077',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('078',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('079',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('DB_PATCHES',CURRENT_TIMESTAMP,'Ranger 
1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_user_module_perm 
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) 
VALUES 
(dbo.getXportalUIdByLoginId('admin'),dbo.getModulesIdByName('Reports'),CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,dbo.getXportalUIdByLoginId('admin'),dbo.getXportalUIdByLoginId('admin'),1);
 INSERT INTO x_user_module_perm 
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) 
VALUES (dbo.getXportalUIdByLoginId('admin'),dbo.getModulesIdByName('Resource 
Based 
Policies'),CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,dbo.getXportalUIdByLoginId('admin'),dbo.getXportalUIdByLoginId('admin'),1);
@@ -4626,5 +4628,6 @@ INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10064',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10065',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10066',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('J10067',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h 
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES 
('JAVA_PATCHES',CURRENT_TIMESTAMP,'Ranger 
1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
diff --git 
a/security-admin/db/sqlserver/patches/079-add-def_text-to-x_service_def.sql 
b/security-admin/db/sqlserver/patches/079-add-def_text-to-x_service_def.sql
new file mode 100644
index 000000000..51e9063b4
--- /dev/null
+++ b/security-admin/db/sqlserver/patches/079-add-def_text-to-x_service_def.sql
@@ -0,0 +1,22 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF 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.
+
+GO
+IF NOT EXISTS(select * from INFORMATION_SCHEMA.columns where table_name = 
'x_service_def' and column_name = 'def_text')
+BEGIN
+       ALTER TABLE [dbo].[x_service_def] ADD [def_text] [nvarchar](max) 
DEFAULT NULL NULL;
+END
+GO
+exit
diff --git 
a/security-admin/src/main/java/org/apache/ranger/biz/ServiceDBStore.java 
b/security-admin/src/main/java/org/apache/ranger/biz/ServiceDBStore.java
index 0d3f5bc1b..83e32d3ff 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/ServiceDBStore.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/ServiceDBStore.java
@@ -115,6 +115,7 @@
 import org.apache.ranger.plugin.model.RangerService;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskTypeDef;
@@ -467,6 +468,7 @@ public RangerServiceDef createServiceDef(RangerServiceDef 
serviceDef) throws Exc
         List<RangerEnumDef>            enums                = 
serviceDef.getEnums();
         RangerDataMaskDef              dataMaskDef          = 
serviceDef.getDataMaskDef();
         RangerRowFilterDef             rowFilterDef         = 
serviceDef.getRowFilterDef();
+        List<RangerConstraintDef>      constraintDefs       = 
serviceDef.getConstraintDefs();
         List<RangerDataMaskTypeDef>    dataMaskTypes        = dataMaskDef == 
null || dataMaskDef.getMaskTypes() == null ? new ArrayList<>() : 
dataMaskDef.getMaskTypes();
         List<RangerAccessTypeDef>      dataMaskAccessTypes  = dataMaskDef == 
null || dataMaskDef.getAccessTypes() == null ? new ArrayList<>() : 
dataMaskDef.getAccessTypes();
         List<RangerResourceDef>        dataMaskResources    = dataMaskDef == 
null || dataMaskDef.getResources() == null ? new ArrayList<>() : 
dataMaskDef.getResources();
@@ -698,6 +700,8 @@ public RangerServiceDef createServiceDef(RangerServiceDef 
serviceDef) throws Exc
             }
         }
 
+        updateServiceDefText(createdSvcDef, constraintDefs);
+
         RangerServiceDef createdServiceDef = 
serviceDefService.getPopulatedViewObject(createdSvcDef);
 
         dataHistService.createObjectDataHistory(createdServiceDef, 
RangerDataHistService.ACTION_CREATE);
@@ -739,6 +743,7 @@ public RangerServiceDef updateServiceDef(RangerServiceDef 
serviceDef) throws Exc
         List<RangerEnumDef>            enums            = 
serviceDef.getEnums() != null ? serviceDef.getEnums() : new ArrayList<>();
         RangerDataMaskDef              dataMaskDef      = 
serviceDef.getDataMaskDef();
         RangerRowFilterDef             rowFilterDef     = 
serviceDef.getRowFilterDef();
+        List<RangerConstraintDef>      constraintDefs   = 
serviceDef.getConstraintDefs();
         RangerServiceDefHelper         defHelper        = new 
RangerServiceDefHelper(serviceDef, false);
 
         defHelper.patchServiceDefWithDefaultValues();
@@ -753,6 +758,8 @@ public RangerServiceDef updateServiceDef(RangerServiceDef 
serviceDef) throws Exc
 
         updateChildObjectsOfServiceDef(createdSvcDef, configs, resources, 
accessTypes, policyConditions, contextEnrichers, enums, dataMaskDef, 
rowFilterDef);
 
+        updateServiceDefText(createdSvcDef, constraintDefs);
+
         RangerServiceDef updatedSvcDef = getServiceDef(serviceDefId);
 
         dataHistService.createObjectDataHistory(updatedSvcDef, 
RangerDataHistService.ACTION_UPDATE);
@@ -3248,6 +3255,17 @@ boolean 
hasServiceConfigForPluginChanged(List<XXServiceConfigMap> dbConfigMaps,
         return ret;
     }
 
+    // Writes x_service_def.def_text after the child rows. constraintDefs are 
stored only in this JSON.
+    private void updateServiceDefText(XXServiceDef xServiceDef, 
List<RangerConstraintDef> constraintDefs) {
+        if (xServiceDef == null) {
+            return;
+        }
+
+        xServiceDef.setDefText(serviceDefService.buildDefText(xServiceDef, 
constraintDefs));
+
+        daoMgr.getXXServiceDef().update(xServiceDef);
+    }
+
     private void updateChildObjectsOfServiceDef(XXServiceDef createdSvcDef, 
List<RangerServiceConfigDef> configs,
             List<RangerResourceDef> resources, List<RangerAccessTypeDef> 
accessTypes,
             List<RangerPolicyConditionDef> policyConditions, 
List<RangerContextEnricherDef> contextEnrichers,
diff --git 
a/security-admin/src/main/java/org/apache/ranger/entity/XXServiceDefBase.java 
b/security-admin/src/main/java/org/apache/ranger/entity/XXServiceDefBase.java
index ba86de242..567d5ace0 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/entity/XXServiceDefBase.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/entity/XXServiceDefBase.java
@@ -93,6 +93,14 @@ public abstract class XXServiceDefBase extends XXDBBase 
implements Serializable
     @Column(name = "def_options")
     protected String defOptions;
 
+    /**
+     * defText of the XXServiceDef
+     * <ul>
+     * </ul>
+     */
+    @Column(name = "def_text")
+    protected String defText;
+
     /**
      * rbKeyLabel of the XXServiceDef
      * <ul>
@@ -238,6 +246,24 @@ public void setDefOptions(String options) {
         this.defOptions = options;
     }
 
+    /**
+     * Returns the value for the member attribute <b>defText</b>
+     *
+     * @return String - value of member attribute <b>defText</b> .
+     */
+    public String getDefText() {
+        return this.defText;
+    }
+
+    /**
+     * This method sets the value to the member attribute <b> defText</b> .
+     *
+     * @param defText Value to set member attribute <b> defText</b>
+     */
+    public void setDefText(String defText) {
+        this.defText = defText;
+    }
+
     /**
      * Returns the value for the member attribute <b>rbKeyLabel</b>
      *
@@ -332,6 +358,7 @@ public boolean equals(Object obj) {
         XXServiceDefBase other = (XXServiceDefBase) obj;
 
         return Objects.equals(description, other.description) &&
+                Objects.equals(defText, other.defText) &&
                 Objects.equals(guid, other.guid) &&
                 Objects.equals(implClassName, other.implClassName) &&
                 Objects.equals(label, other.label) &&
diff --git 
a/security-admin/src/main/java/org/apache/ranger/patch/PatchForServiceDefJson_J10067.java
 
b/security-admin/src/main/java/org/apache/ranger/patch/PatchForServiceDefJson_J10067.java
new file mode 100644
index 000000000..d95a5a0a1
--- /dev/null
+++ 
b/security-admin/src/main/java/org/apache/ranger/patch/PatchForServiceDefJson_J10067.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.ranger.patch;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.db.RangerDaoManager;
+import org.apache.ranger.entity.XXServiceDef;
+import org.apache.ranger.service.RangerServiceDefService;
+import org.apache.ranger.util.CLIUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.util.List;
+
+@Component
+public class PatchForServiceDefJson_J10067 extends BaseLoader {
+    private static final Logger logger = 
LoggerFactory.getLogger(PatchForServiceDefJson_J10067.class);
+
+    @Autowired
+    RangerDaoManager daoMgr;
+
+    @Autowired
+    RangerServiceDefService serviceDefService;
+
+    @Autowired
+    @Qualifier(value = "transactionManager")
+    PlatformTransactionManager txManager;
+
+    public static void main(String[] args) {
+        logger.info("main()");
+        try {
+            PatchForServiceDefJson_J10067 loader = 
(PatchForServiceDefJson_J10067) 
CLIUtil.getBean(PatchForServiceDefJson_J10067.class);
+
+            loader.init();
+
+            while (loader.isMoreToProcess()) {
+                loader.load();
+            }
+
+            logger.info("Load complete. Exiting!!!");
+            System.exit(0);
+        } catch (Exception e) {
+            logger.error("Error loading", e);
+            System.exit(1);
+        }
+    }
+
+    @Override
+    public void init() throws Exception {
+        // Do Nothing
+    }
+
+    @Override
+    public void printStats() {
+        logger.info("PatchForServiceDefJson_J10067");
+    }
+
+    @Override
+    public void execLoad() {
+        logger.info("==> PatchForServiceDefJson_J10067.execLoad()");
+
+        try {
+            updateServiceDefText();
+        } catch (Exception e) {
+            logger.error("Error while applying PatchForServiceDefJson_J10067", 
e);
+            throw new RuntimeException("PatchForServiceDefJson_J10067 failed", 
e);
+        }
+
+        logger.info("<== PatchForServiceDefJson_J10067.execLoad()");
+    }
+
+    private void updateServiceDefText() {
+        List<XXServiceDef> serviceDefs = daoMgr.getXXServiceDef().getAll();
+
+        if (CollectionUtils.isEmpty(serviceDefs)) {
+            logger.info("No service-defs found");
+
+            return;
+        }
+
+        TransactionTemplate txTemplate = new TransactionTemplate(txManager);
+
+        
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+
+        for (XXServiceDef xServiceDef : serviceDefs) {
+            Long serviceDefId = xServiceDef.getId();
+
+            txTemplate.execute(status -> {
+                XXServiceDef dbServiceDef = 
daoMgr.getXXServiceDef().getById(serviceDefId);
+
+                if (dbServiceDef != null && 
StringUtils.isEmpty(dbServiceDef.getDefText())) {
+                    // constraintDefs were not stored before def_text existed, 
so there are none to carry over
+                    String defText = 
serviceDefService.buildDefText(dbServiceDef, null);
+
+                    if (StringUtils.isEmpty(defText)) {
+                        throw new IllegalStateException("failed to build JSON 
for service-def(id=" + serviceDefId + ", name=" + dbServiceDef.getName() + ")");
+                    }
+
+                    dbServiceDef.setDefText(defText);
+
+                    daoMgr.getXXServiceDef().update(dbServiceDef);
+
+                    logger.info("Stored JSON for service-def(id={}, name={})", 
serviceDefId, dbServiceDef.getName());
+                } else if (dbServiceDef != null) {
+                    logger.info("JSON already present for service-def(id={}, 
name={}); skipping", serviceDefId, dbServiceDef.getName());
+                }
+
+                return null;
+            });
+        }
+    }
+}
diff --git 
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefServiceBase.java
 
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefServiceBase.java
index 1b93b2c84..3edaf6778 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefServiceBase.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefServiceBase.java
@@ -19,6 +19,7 @@
 
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.authorization.utils.JsonUtils;
 import org.apache.ranger.common.AppConstants;
 import org.apache.ranger.common.GUIDUtil;
 import org.apache.ranger.common.JSONUtil;
@@ -41,6 +42,7 @@
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef.AccessTypeCategory;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskTypeDef;
@@ -557,6 +559,39 @@ protected T mapViewToEntityBean(V vObj, T xObj, int 
operationContext) {
 
     @Override
     protected V mapEntityToViewBean(V vObj, T xObj) {
+        return mapServiceDefColumns(vObj, xObj);
+    }
+
+    @Override
+    protected V populateViewBean(T xServiceDef) {
+        V serviceDef = super.populateViewBean(xServiceDef);
+
+        if (!applyDefText(serviceDef, xServiceDef.getDefText())) {
+            loadChildTables(serviceDef, xServiceDef);
+        }
+
+        
serviceDef.setMarkerAccessTypes(ServiceDefUtil.getMarkerAccessTypes(serviceDef.getAccessTypes()));
+        addImplicitConditionExpressionIfNeeded(serviceDef);
+        ServiceDefUtil.normalize(serviceDef);
+
+        return serviceDef;
+    }
+
+    // JSON for x_service_def.def_text, built after the child rows are written.
+    // Marker access types, the implicit condition, and read-time options are 
left out.
+    public String buildDefText(T xServiceDef, List<RangerConstraintDef> 
constraintDefs) {
+        V serviceDef = createViewObject();
+
+        serviceDef.setId(xServiceDef.getId());
+        mapServiceDefColumns(serviceDef, xServiceDef);
+        loadChildTables(serviceDef, xServiceDef);
+        serviceDef.setConstraintDefs(constraintDefs);
+        serviceDef.setMarkerAccessTypes(null);
+
+        return JsonUtils.objectToJson(serviceDef);
+    }
+
+    private V mapServiceDefColumns(V vObj, T xObj) {
         vObj.setGuid(xObj.getGuid());
         vObj.setVersion(xObj.getVersion());
         vObj.setName(xObj.getName());
@@ -572,9 +607,33 @@ protected V mapEntityToViewBean(V vObj, T xObj) {
         return vObj;
     }
 
-    @Override
-    protected V populateViewBean(T xServiceDef) {
-        V                        serviceDef   = 
super.populateViewBean(xServiceDef);
+    private boolean applyDefText(V serviceDef, String defText) {
+        if (StringUtils.isEmpty(defText)) {
+            return false;
+        }
+
+        V fromJson = JsonUtils.jsonToObject(defText, tViewClass);
+
+        if (fromJson == null) {
+            LOG.warn("failed to read def_text of service-def(id={}, name={}); 
loading it from child tables", serviceDef.getId(), serviceDef.getName());
+
+            return false;
+        }
+
+        serviceDef.setConfigs(fromJson.getConfigs());
+        serviceDef.setResources(fromJson.getResources());
+        serviceDef.setAccessTypes(fromJson.getAccessTypes());
+        serviceDef.setPolicyConditions(fromJson.getPolicyConditions());
+        serviceDef.setConstraintDefs(fromJson.getConstraintDefs());
+        serviceDef.setContextEnrichers(fromJson.getContextEnrichers());
+        serviceDef.setEnums(fromJson.getEnums());
+        serviceDef.setDataMaskDef(fromJson.getDataMaskDef());
+        serviceDef.setRowFilterDef(fromJson.getRowFilterDef());
+
+        return true;
+    }
+
+    private void loadChildTables(V serviceDef, T xServiceDef) {
         Long                     serviceDefId = xServiceDef.getId();
         List<XXServiceConfigDef> xConfigs     = 
daoMgr.getXXServiceConfigDef().findByServiceDefId(serviceDefId);
 
@@ -619,8 +678,6 @@ protected V populateViewBean(T xServiceDef) {
             serviceDef.setAccessTypes(accessTypes);
         }
 
-        
serviceDef.setMarkerAccessTypes(ServiceDefUtil.getMarkerAccessTypes(serviceDef.getAccessTypes()));
-
         List<XXPolicyConditionDef> xPolicyConditions = 
daoMgr.getXXPolicyConditionDef().findByServiceDefId(serviceDefId);
 
         if (!stringUtil.isEmpty(xPolicyConditions)) {
@@ -713,12 +770,6 @@ protected V populateViewBean(T xServiceDef) {
 
         serviceDef.setDataMaskDef(dataMaskDef);
         serviceDef.setRowFilterDef(rowFilterDef);
-
-        addImplicitConditionExpressionIfNeeded(serviceDef);
-
-        ServiceDefUtil.normalize(serviceDef);
-
-        return serviceDef;
     }
 
     protected Map<String, String> jsonStringToMap(String jsonStr) {
diff --git 
a/security-admin/src/test/java/org/apache/ranger/biz/TestServiceDBStore.java 
b/security-admin/src/test/java/org/apache/ranger/biz/TestServiceDBStore.java
index 5a36e7f29..fc1106a82 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestServiceDBStore.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestServiceDBStore.java
@@ -116,6 +116,7 @@
 import org.apache.ranger.plugin.model.RangerService;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
@@ -284,7 +285,11 @@ public void test11createServiceDef() throws Exception {
         List<XXResourceDef> xResourceDefs = new ArrayList<>();
         xResourceDefs.add(xResourceDef);
 
-        RangerServiceDef serviceDef = new RangerServiceDef();
+        RangerServiceDef          serviceDef     = new RangerServiceDef();
+        List<RangerConstraintDef> constraintDefs = 
Collections.singletonList(constraintDef());
+
+        serviceDef.setConstraintDefs(constraintDefs);
+
         
Mockito.when(serviceDefService.create(serviceDef)).thenReturn(serviceDef);
 
         Mockito.when(daoMgr.getXXServiceDef()).thenReturn(xServiceDefDao);
@@ -323,6 +328,8 @@ public void test11createServiceDef() throws Exception {
         Assertions.assertEquals(dbServiceDef.getResources(), 
serviceDef.getResources());
         Mockito.verify(serviceDefService).getPopulatedViewObject(xServiceDef);
         Mockito.verify(serviceDefService).create(serviceDef);
+        Mockito.verify(serviceDefService).buildDefText(xServiceDef, 
constraintDefs);
+        Mockito.verify(xServiceDefDao).update(xServiceDef);
         Mockito.verify(daoMgr).getXXServiceConfigDef();
         Mockito.verify(daoMgr).getXXEnumDef();
         Mockito.verify(daoMgr).getXXAccessTypeDef();
@@ -342,8 +349,11 @@ public void test12updateServiceDef() throws Exception {
         XXDataMaskTypeDefDao xDataMaskDefDao           = 
Mockito.mock(XXDataMaskTypeDefDao.class);
         XXServiceDao         xServiceDao               = 
Mockito.mock(XXServiceDao.class);
 
-        RangerServiceDef rangerServiceDef = rangerServiceDef();
-        Long             serviceDefId     = rangerServiceDef.getId();
+        RangerServiceDef          rangerServiceDef = rangerServiceDef();
+        Long                      serviceDefId     = rangerServiceDef.getId();
+        List<RangerConstraintDef> constraintDefs   = 
Collections.singletonList(constraintDef());
+
+        rangerServiceDef.setConstraintDefs(constraintDefs);
 
         List<XXServiceConfigDef> svcConfDefList      = new ArrayList<>();
         XXServiceConfigDef       serviceConfigDefObj = new 
XXServiceConfigDef();
@@ -444,6 +454,9 @@ public void test12updateServiceDef() throws Exception {
         Assertions.assertEquals(dbServiceDef.getConfigs(), 
rangerServiceDef.getConfigs());
         Assertions.assertEquals(dbServiceDef.getVersion(), 
rangerServiceDef.getVersion());
         Assertions.assertEquals(dbServiceDef.getResources(), 
rangerServiceDef.getResources());
+        // update() returns a different object, so this checks the request's 
constraintDefs were kept
+        Mockito.verify(serviceDefService).buildDefText(xServiceDef, 
constraintDefs);
+        Mockito.verify(xServiceDefDao).update(xServiceDef);
     }
 
     @Test
@@ -6961,6 +6974,25 @@ private RangerServiceDef rangerServiceDef() {
         return rangerServiceDef;
     }
 
+    private RangerConstraintDef constraintDef() {
+        RangerServiceDef.RangerConstraintParamDef approver = new 
RangerServiceDef.RangerConstraintParamDef();
+
+        approver.setItemId(1L);
+        approver.setName("approver");
+        approver.setType("string");
+        approver.setMandatory(true);
+
+        RangerConstraintDef constraintDef = new RangerConstraintDef();
+
+        constraintDef.setItemId(1L);
+        constraintDef.setName("requireApproval");
+        constraintDef.setResources(Collections.singletonList("tool"));
+        constraintDef.setAccessTypes(Collections.singletonList("execute"));
+        constraintDef.setParameters(Collections.singletonList(approver));
+
+        return constraintDef;
+    }
+
     private RangerService rangerService() {
         Map<String, String> configs = new HashMap<>();
         configs.put("username", "servicemgr");
diff --git 
a/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForServiceDefJson_J10067.java
 
b/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForServiceDefJson_J10067.java
new file mode 100644
index 000000000..b306d823f
--- /dev/null
+++ 
b/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForServiceDefJson_J10067.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.ranger.patch;
+
+import org.apache.ranger.db.RangerDaoManager;
+import org.apache.ranger.db.XXServiceDefDao;
+import org.apache.ranger.entity.XXServiceDef;
+import org.apache.ranger.service.RangerServiceDefService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+@ExtendWith(MockitoExtension.class)
+public class TestPatchForServiceDefJson_J10067 {
+    private RangerDaoManager               daoMgr;
+    private XXServiceDefDao                xServiceDefDao;
+    private RangerServiceDefService        serviceDefService;
+    private PatchForServiceDefJson_J10067  patch;
+
+    @BeforeEach
+    public void setUp() {
+        daoMgr            = Mockito.mock(RangerDaoManager.class);
+        xServiceDefDao    = Mockito.mock(XXServiceDefDao.class);
+        serviceDefService = Mockito.mock(RangerServiceDefService.class);
+
+        PlatformTransactionManager txManager = 
Mockito.mock(PlatformTransactionManager.class);
+
+        
Mockito.lenient().when(txManager.getTransaction(Mockito.any())).thenReturn(Mockito.mock(TransactionStatus.class));
+        Mockito.when(daoMgr.getXXServiceDef()).thenReturn(xServiceDefDao);
+
+        patch                   = new PatchForServiceDefJson_J10067();
+        patch.daoMgr            = daoMgr;
+        patch.serviceDefService = serviceDefService;
+        patch.txManager         = txManager;
+    }
+
+    @Test
+    public void testStoresJsonOnlyForServiceDefsWithoutIt() {
+        XXServiceDef withoutJson = serviceDef(1L, "hive", null);
+        XXServiceDef withJson    = serviceDef(2L, "hdfs", 
"{\"name\":\"hdfs\"}");
+
+        Mockito.when(xServiceDefDao.getAll()).thenReturn(new 
ArrayList<>(Arrays.asList(withoutJson, withJson)));
+        Mockito.when(xServiceDefDao.getById(1L)).thenReturn(withoutJson);
+        Mockito.when(xServiceDefDao.getById(2L)).thenReturn(withJson);
+        Mockito.when(serviceDefService.buildDefText(withoutJson, 
null)).thenReturn("{\"name\":\"hive\"}");
+
+        patch.execLoad();
+
+        Assertions.assertEquals("{\"name\":\"hive\"}", 
withoutJson.getDefText());
+        Assertions.assertEquals("{\"name\":\"hdfs\"}", withJson.getDefText());
+        Mockito.verify(xServiceDefDao).update(withoutJson);
+        Mockito.verify(xServiceDefDao, Mockito.never()).update(withJson);
+        Mockito.verify(serviceDefService, 
Mockito.never()).buildDefText(Mockito.eq(withJson), Mockito.any());
+    }
+
+    @Test
+    public void testFailsWhenJsonCannotBeBuilt() {
+        XXServiceDef withoutJson = serviceDef(1L, "hive", null);
+
+        Mockito.when(xServiceDefDao.getAll()).thenReturn(new 
ArrayList<>(Collections.singletonList(withoutJson)));
+        Mockito.when(xServiceDefDao.getById(1L)).thenReturn(withoutJson);
+        Mockito.when(serviceDefService.buildDefText(withoutJson, 
null)).thenReturn(null);
+
+        Assertions.assertThrows(RuntimeException.class, () -> 
patch.execLoad());
+        Mockito.verify(xServiceDefDao, Mockito.never()).update(Mockito.any());
+    }
+
+    @Test
+    public void testNoServiceDefs() {
+        Mockito.when(xServiceDefDao.getAll()).thenReturn(new ArrayList<>());
+
+        patch.execLoad();
+        patch.printStats();
+
+        Mockito.verify(xServiceDefDao, Mockito.never()).update(Mockito.any());
+    }
+
+    private static XXServiceDef serviceDef(Long id, String name, String 
defText) {
+        XXServiceDef ret = new XXServiceDef();
+
+        ret.setId(id);
+        ret.setName(name);
+        ret.setDefText(defText);
+
+        return ret;
+    }
+}
diff --git 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
index 41d0be2d9..a86338b4a 100644
--- 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
+++ 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
@@ -18,8 +18,10 @@
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ranger.authorization.hadoop.config.RangerAdminConfig;
+import org.apache.ranger.authorization.utils.JsonUtils;
 import org.apache.ranger.biz.RangerBizUtil;
 import org.apache.ranger.common.ContextUtil;
+import org.apache.ranger.common.JSONUtil;
 import org.apache.ranger.common.PropertiesUtil;
 import org.apache.ranger.common.StringUtil;
 import org.apache.ranger.common.UserSessionBase;
@@ -50,6 +52,7 @@
 import org.apache.ranger.entity.XXServiceDefWithAssignedId;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
@@ -71,6 +74,7 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -85,6 +89,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -911,6 +916,202 @@ public void testValidateForUpdate() throws 
NoSuchMethodException {
         }
     }
 
+    @Test
+    public void test6PopulateViewBeanLoadsConstraintDefs() throws Exception {
+        serviceDefService.jsonUtil = new JSONUtil();
+
+        RangerConstraintDef constraintDef = new RangerConstraintDef();
+        constraintDef.setItemId(4L);
+        constraintDef.setName("requireApproval");
+        constraintDef.setResources(Arrays.asList("database", "udf"));
+        constraintDef.setAccessTypes(Collections.singletonList("select"));
+        RangerServiceDef.RangerConstraintParamDef approver = new 
RangerServiceDef.RangerConstraintParamDef();
+        approver.setItemId(1L);
+        approver.setName("approver");
+        approver.setType("string");
+        approver.setMandatory(true);
+        constraintDef.getParameters().add(approver);
+
+        RangerServiceDef jsonBody = new RangerServiceDef();
+        jsonBody.setName("json-name");
+        jsonBody.getConstraintDefs().add(constraintDef);
+
+        XXServiceDef serviceDef = serviceDef();
+        serviceDef.setAddedByUserId(null);
+        serviceDef.setUpdatedByUserId(null);
+        serviceDef.setName("hdfs");
+        serviceDef.setDefOptions("{}");
+        serviceDef.setDefText(serviceDefService.objectToJson(jsonBody));
+
+        RangerServiceDef loaded = 
serviceDefService.populateViewBean(serviceDef);
+
+        assertNotNull(loaded.getConstraintDefs());
+        assertEquals(serviceDef.getName(), loaded.getName());
+        assertEquals(1, loaded.getConstraintDefs().size());
+        assertEquals(Long.valueOf(4L), 
loaded.getConstraintDefs().get(0).getItemId());
+        assertEquals("requireApproval", 
loaded.getConstraintDefs().get(0).getName());
+        assertEquals(Arrays.asList("database", "udf"), 
loaded.getConstraintDefs().get(0).getResources());
+        assertEquals(Collections.singletonList("select"), 
loaded.getConstraintDefs().get(0).getAccessTypes());
+        assertEquals("approver", 
loaded.getConstraintDefs().get(0).getParameters().get(0).getName());
+        assertEquals("string", 
loaded.getConstraintDefs().get(0).getParameters().get(0).getType());
+    }
+
+    @Test
+    public void testPopulateViewBeanReadsDefText() {
+        mockBaseFieldsForPopulateViewBean();
+
+        RangerResourceDef tool = new RangerResourceDef();
+        tool.setItemId(1L);
+        tool.setName("tool");
+        tool.setLevel(10);
+
+        RangerAccessTypeDef execute = new RangerAccessTypeDef();
+        execute.setItemId(1L);
+        execute.setName("execute");
+
+        RangerServiceDef stored = new RangerServiceDef();
+        stored.setName("stale-name-in-json");
+        stored.setResources(Collections.singletonList(tool));
+        stored.setAccessTypes(Collections.singletonList(execute));
+        stored.setConstraintDefs(Collections.singletonList(constraintDef()));
+
+        XXServiceDef xServiceDef = serviceDef();
+        xServiceDef.setName("agent");
+        xServiceDef.setDefOptions("{}");
+        xServiceDef.setDefText(JsonUtils.objectToJson(stored));
+
+        RangerServiceDef loaded = 
serviceDefService.populateViewBean(xServiceDef);
+
+        assertEquals("agent", loaded.getName());
+        assertEquals(1, loaded.getResources().size());
+        assertEquals("tool", loaded.getResources().get(0).getName());
+        assertEquals("execute", loaded.getAccessTypes().get(0).getName());
+        assertEquals(1, loaded.getConstraintDefs().size());
+        assertEquals("requireApproval", 
loaded.getConstraintDefs().get(0).getName());
+        assertEquals(Collections.singletonList("tool"), 
loaded.getConstraintDefs().get(0).getResources());
+        assertEquals("approver", 
loaded.getConstraintDefs().get(0).getParameters().get(0).getName());
+        verify(daoMgr, never()).getXXResourceDef();
+        verify(daoMgr, never()).getXXAccessTypeDef();
+        verify(daoMgr, never()).getXXServiceConfigDef();
+    }
+
+    @Test
+    public void testPopulateViewBeanFallsBackToChildTables() {
+        mockBaseFieldsForPopulateViewBean();
+        mockEmptyChildTables(Id);
+
+        XXServiceDef xServiceDef = serviceDef();
+        xServiceDef.setDefOptions("{}");
+        xServiceDef.setDefText("not-a-json");
+
+        RangerServiceDef loaded = 
serviceDefService.populateViewBean(xServiceDef);
+
+        assertNotNull(loaded);
+        assertTrue(loaded.getConstraintDefs().isEmpty());
+        verify(daoMgr).getXXResourceDef();
+        verify(daoMgr).getXXAccessTypeDef();
+    }
+
+    @Test
+    public void testBuildDefTextIncludesConstraintDefsAndNoDerivedValues() {
+        serviceDefService.stringUtil = new StringUtil();
+        serviceDefService.jsonUtil   = new JSONUtil();
+
+        mockEmptyChildTables(Id);
+
+        XXServiceDef xServiceDef = serviceDef();
+        xServiceDef.setName("agent");
+        xServiceDef.setDefOptions("{\"custom\":\"value\"}");
+
+        String defText = serviceDefService.buildDefText(xServiceDef, 
Collections.singletonList(constraintDef()));
+
+        assertNotNull(defText);
+        assertFalse(defText.contains("markerAccessTypes"));
+        
assertFalse(defText.contains(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES));
+
+        RangerServiceDef stored = JsonUtils.jsonToObject(defText, 
RangerServiceDef.class);
+
+        assertNotNull(stored);
+        assertEquals("agent", stored.getName());
+        assertEquals("value", stored.getOptions().get("custom"));
+        assertEquals(1, stored.getConstraintDefs().size());
+        assertEquals("requireApproval", 
stored.getConstraintDefs().get(0).getName());
+        assertEquals(Arrays.asList("execute"), 
stored.getConstraintDefs().get(0).getAccessTypes());
+    }
+
+    @Test
+    public void testBuildDefTextWithoutConstraintDefs() {
+        serviceDefService.stringUtil = new StringUtil();
+
+        mockEmptyChildTables(Id);
+
+        String           defText = 
serviceDefService.buildDefText(serviceDef(), null);
+        RangerServiceDef stored  = JsonUtils.jsonToObject(defText, 
RangerServiceDef.class);
+
+        assertNotNull(stored);
+        assertTrue(stored.getConstraintDefs() == null || 
stored.getConstraintDefs().isEmpty());
+    }
+
+    private void mockBaseFieldsForPopulateViewBean() {
+        serviceDefService.bizUtil    = Mockito.mock(RangerBizUtil.class);
+        serviceDefService.stringUtil = new StringUtil();
+        serviceDefService.jsonUtil   = new JSONUtil();
+
+        XXPortalUserDao xPortalUserDao = Mockito.mock(XXPortalUserDao.class);
+        XXPortalUser    portalUser     = new XXPortalUser();
+
+        portalUser.setId(Id);
+        portalUser.setFirstName("Admin");
+
+        
Mockito.lenient().when(daoMgr.getXXPortalUser()).thenReturn(xPortalUserDao);
+        
Mockito.lenient().when(xPortalUserDao.getById(Id)).thenReturn(portalUser);
+    }
+
+    private void mockEmptyChildTables(Long serviceDefId) {
+        XXServiceConfigDefDao    xServiceConfigDefDao    = 
Mockito.mock(XXServiceConfigDefDao.class);
+        XXResourceDefDao         xResourceDefDao         = 
Mockito.mock(XXResourceDefDao.class);
+        XXAccessTypeDefDao       xAccessTypeDefDao       = 
Mockito.mock(XXAccessTypeDefDao.class);
+        XXAccessTypeDefGrantsDao xAccessTypeDefGrantsDao = 
Mockito.mock(XXAccessTypeDefGrantsDao.class);
+        XXPolicyConditionDefDao  xPolicyConditionDefDao  = 
Mockito.mock(XXPolicyConditionDefDao.class);
+        XXContextEnricherDefDao  xContextEnricherDefDao  = 
Mockito.mock(XXContextEnricherDefDao.class);
+        XXEnumDefDao             xEnumDefDao             = 
Mockito.mock(XXEnumDefDao.class);
+        XXDataMaskTypeDefDao     xDataMaskTypeDefDao     = 
Mockito.mock(XXDataMaskTypeDefDao.class);
+
+        
Mockito.when(daoMgr.getXXServiceConfigDef()).thenReturn(xServiceConfigDefDao);
+        
Mockito.when(xServiceConfigDefDao.findByServiceDefId(serviceDefId)).thenReturn(new
 ArrayList<>());
+        Mockito.when(daoMgr.getXXResourceDef()).thenReturn(xResourceDefDao);
+        
Mockito.when(xResourceDefDao.findByServiceDefId(serviceDefId)).thenReturn(new 
ArrayList<>());
+        
Mockito.when(daoMgr.getXXAccessTypeDef()).thenReturn(xAccessTypeDefDao);
+        
Mockito.when(xAccessTypeDefDao.findByServiceDefId(serviceDefId)).thenReturn(new 
ArrayList<>());
+        
Mockito.when(daoMgr.getXXAccessTypeDefGrants()).thenReturn(xAccessTypeDefGrantsDao);
+        
Mockito.when(xAccessTypeDefGrantsDao.findImpliedGrantsByServiceDefId(serviceDefId)).thenReturn(Collections.emptyMap());
+        
Mockito.when(daoMgr.getXXPolicyConditionDef()).thenReturn(xPolicyConditionDefDao);
+        
Mockito.when(xPolicyConditionDefDao.findByServiceDefId(serviceDefId)).thenReturn(new
 ArrayList<>());
+        
Mockito.when(daoMgr.getXXContextEnricherDef()).thenReturn(xContextEnricherDefDao);
+        
Mockito.when(xContextEnricherDefDao.findByServiceDefId(serviceDefId)).thenReturn(new
 ArrayList<>());
+        Mockito.when(daoMgr.getXXEnumDef()).thenReturn(xEnumDefDao);
+        
Mockito.when(xEnumDefDao.findByServiceDefId(serviceDefId)).thenReturn(new 
ArrayList<>());
+        
Mockito.when(daoMgr.getXXDataMaskTypeDef()).thenReturn(xDataMaskTypeDefDao);
+        
Mockito.when(xDataMaskTypeDefDao.findByServiceDefId(serviceDefId)).thenReturn(new
 ArrayList<>());
+    }
+
+    private RangerConstraintDef constraintDef() {
+        RangerServiceDef.RangerConstraintParamDef approver = new 
RangerServiceDef.RangerConstraintParamDef();
+        approver.setItemId(1L);
+        approver.setName("approver");
+        approver.setType("string");
+        approver.setMandatory(true);
+
+        RangerConstraintDef ret = new RangerConstraintDef();
+        ret.setItemId(1L);
+        ret.setName("requireApproval");
+        ret.setResources(Collections.singletonList("tool"));
+        ret.setAccessTypes(Collections.singletonList("execute"));
+        ret.setParameters(Collections.singletonList(approver));
+
+        return ret;
+    }
+
     private boolean hasActionMatchesCondition(RangerServiceDef serviceDef) {
         if (serviceDef.getPolicyConditions() == null) {
             return false;
diff --git 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefServiceBase.java
 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefServiceBase.java
index b58f047c7..e9029b360 100644
--- 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefServiceBase.java
+++ 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefServiceBase.java
@@ -38,6 +38,8 @@
 import org.apache.ranger.entity.XXServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintDef;
+import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerConstraintParamDef;
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef;
@@ -62,9 +64,12 @@
 import javax.ws.rs.WebApplicationException;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -589,6 +594,94 @@ public void test21searchRangerServiceDefs() {
         Assertions.assertNotNull(dbRangerServiceDefList);
     }
 
+    @Test
+    public void test22mapViewToEntityBeanLeavesDefTextUnset() {
+        RangerServiceDef view = rangerServiceDef();
+        view.setGuid("column-guid");
+        view.setName("hdfs");
+        view.getConstraintDefs().add(constraintDef());
+
+        XXServiceDef stored = 
rangerServiceDefService.mapViewToEntityBean(view, serviceDef(), 1);
+
+        Assertions.assertNull(stored.getDefText());
+    }
+
+    @Test
+    public void test23populateViewBeanReadsDefTextWithoutOverwritingColumns() 
throws Exception {
+        JSONUtil jsonUtil = new JSONUtil();
+        rangerServiceDefService.jsonUtil = jsonUtil;
+
+        RangerServiceDef jsonBody = new RangerServiceDef();
+        jsonBody.setName("json-name");
+        jsonBody.setGuid("json-guid");
+        jsonBody.setLabel("json-label");
+        jsonBody.setDescription("json-description");
+        Map<String, String> jsonOptions = new HashMap<>();
+        jsonOptions.put("fromJson", "yes");
+        jsonBody.setOptions(jsonOptions);
+        jsonBody.getConstraintDefs().add(constraintDef());
+
+        XXServiceDef row = serviceDef();
+        row.setAddedByUserId(null);
+        row.setUpdatedByUserId(null);
+        row.setName("hdfs");
+        row.setGuid("column-guid");
+        row.setLabel("column-label");
+        row.setDescription("column-description");
+        row.setDefOptions("{\"stored\":\"yes\"}");
+        row.setDefText(jsonUtil.writeObjectAsString(jsonBody));
+
+        RangerServiceDef loaded = 
rangerServiceDefService.populateViewBean(row);
+
+        Assertions.assertEquals("hdfs", loaded.getName());
+        Assertions.assertEquals("column-guid", loaded.getGuid());
+        Assertions.assertEquals("column-label", loaded.getLabel());
+        Assertions.assertEquals("column-description", loaded.getDescription());
+        Assertions.assertEquals("yes", loaded.getOptions().get("stored"));
+        Assertions.assertNull(loaded.getOptions().get("fromJson"));
+        Assertions.assertEquals(1, loaded.getConstraintDefs().size());
+        Assertions.assertEquals("requireApproval", 
loaded.getConstraintDefs().get(0).getName());
+        Assertions.assertEquals(Arrays.asList("database", "udf"), 
loaded.getConstraintDefs().get(0).getResources());
+        Assertions.assertEquals(Collections.singletonList("select"), 
loaded.getConstraintDefs().get(0).getAccessTypes());
+        Assertions.assertEquals("approver", 
loaded.getConstraintDefs().get(0).getParameters().get(0).getName());
+        Assertions.assertEquals("string", 
loaded.getConstraintDefs().get(0).getParameters().get(0).getType());
+        Assertions.assertEquals("authType", 
loaded.getConstraintDefs().get(0).getParameters().get(1).getSubType());
+    }
+
+    private RangerConstraintDef constraintDef() {
+        RangerConstraintParamDef approver = new RangerConstraintParamDef();
+        approver.setItemId(1L);
+        approver.setName("approver");
+        approver.setType("string");
+        approver.setMandatory(true);
+
+        RangerConstraintParamDef mode = new RangerConstraintParamDef();
+        mode.setItemId(2L);
+        mode.setName("mode");
+        mode.setType("enum");
+        mode.setSubType("authType");
+        mode.setMandatory(false);
+        mode.setDefaultValue("simple");
+
+        List<String> resources = new ArrayList<>();
+        resources.add("database");
+        resources.add("udf");
+        List<String> accessTypes = new ArrayList<>();
+        accessTypes.add("select");
+        List<RangerConstraintParamDef> parameters = new ArrayList<>();
+        parameters.add(approver);
+        parameters.add(mode);
+
+        RangerConstraintDef constraintDef = new RangerConstraintDef();
+        constraintDef.setItemId(4L);
+        constraintDef.setName("requireApproval");
+        constraintDef.setResources(resources);
+        constraintDef.setAccessTypes(accessTypes);
+        constraintDef.setParameters(parameters);
+
+        return constraintDef;
+    }
+
     private RangerServiceDef rangerServiceDef() {
         List<RangerServiceConfigDef>   configs          = new ArrayList<>();
         List<RangerResourceDef>        resources        = new ArrayList<>();

Reply via email to