public boolean validate(
      Policies policies, TimePartitionSpec timePartitioning, TableUri tableUri, String schema) {

    if (policies != null && policies.getRetention() != null) {
      // Two invalid case for timePartitioned table
      if (timePartitioning != null) {
        if (policies.getRetention().getColumnPattern() != null) {
          failureMessage =
              String.format(
                  "You can only specify retention column pattern on non-timestampPartitioned table (table[%s] is time-partitioned by[%s])",
                  tableUri, timePartitioning.getColumnName());
          return false;
        }
        if (!policies.getRetention().getGranularity().equals(timePartitioning.getGranularity())) {
          failureMessage =
              String.format(
                  "invalid policies retention granularity format for table %s. Policies granularity must be equal to or lesser than"
                      + " time partition spec granularity",
                  tableUri);
          errorField = "retention";
          return false;
        }
      }

      // invalid cases regarding the integrity of retention object.
      if (!validateGranularityWithPattern(policies.getRetention())) {
        failureMessage =
            String.format(
                "Provided Retention Granularity[%s] is not supported with default pattern. "
                    + "Please define pattern in retention config or use one of supported granularity: %s",
                policies.getRetention().getGranularity().name(),
                Arrays.toString(DefaultColumnPattern.values()));
        return false;
      }
      if (!validatePatternIfPresent(policies.getRetention(), tableUri, schema)) {
        failureMessage =
            String.format(
                "Provided pattern[%s] is not recognizable by OpenHouse for the table[%s]; Also please make sure the declared column is part of table schema.",
                policies.getRetention().getColumnPattern(), tableUri);
        return false;
      }
      if (timePartitioning == null && policies.getRetention().getColumnPattern() == null) {
        failureMessage =
            String.format(
                "For non timestamp-partitioned table %s, column pattern in retention policy is mandatory",
                tableUri);
        return false;
      }
    }
    return true;
  }
@Test
  void testValidate() {
    // Negative: declared retention column not exists
    RetentionColumnPattern pattern0 =
        RetentionColumnPattern.builder()
            .pattern("yyyy-mm-dd-hh")
            .columnName("bb")
            .build(); /* dummySchema doesn't have bb*/
    Retention retention0 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.DAY)
            .columnPattern(pattern0)
            .build();
    Policies policies0 = Policies.builder().retention(retention0).build();
    Assertions.assertFalse(
        validator.validate(
            policies0, null, TableUri.builder().build(), getSchemaJsonFromSchema(dummySchema)));

    pattern0 =
        RetentionColumnPattern.builder()
            .pattern("yyyy-mm-dd-hh")
            .columnName("Aa") /* casing matters*/
            .build();
    retention0 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.DAY)
            .columnPattern(pattern0)
            .build();
    policies0 = Policies.builder().retention(retention0).build();
    Assertions.assertFalse(
        validator.validate(
            policies0, null, TableUri.builder().build(), getSchemaJsonFromSchema(dummySchema)));

    pattern0 =
        RetentionColumnPattern.builder()
            .pattern("yyyy-mm-dd-hh")
            .columnName("top1.aaa") /* negative case for nested*/
            .build();
    retention0 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.DAY)
            .columnPattern(pattern0)
            .build();
    policies0 = Policies.builder().retention(retention0).build();
    Assertions.assertFalse(
        validator.validate(
            policies0, null, TableUri.builder().build(), getSchemaJsonFromSchema(nestedSchema)));

    // Negative: Missing timepartitionspec AND pattern
    Retention retention1 =
        Retention.builder().count(1).granularity(TimePartitionSpec.Granularity.DAY).build();
    Policies policies1 = Policies.builder().retention(retention1).build();
    Assertions.assertFalse(
        validator.validate(
            policies1, null, TableUri.builder().build(), getSchemaJsonFromSchema(dummySchema)));

    // Positive: Only have pattern but no timepartitionSpec
    RetentionColumnPattern pattern =
        RetentionColumnPattern.builder().pattern("yyyy-mm-dd-hh").build();
    Retention retention2 = retention1.toBuilder().columnPattern(pattern).build();
    Policies policies2 = Policies.builder().retention(retention2).build();
    Assertions.assertTrue(
        validator.validate(
            policies2, null, TableUri.builder().build(), getSchemaJsonFromSchema(dummySchema)));

    // Negative: Having both timepartitionspec AND pattern
    Retention retention3 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.DAY)
            .columnPattern(pattern)
            .build();
    Policies policies3 = Policies.builder().retention(retention3).build();
    Assertions.assertFalse(
        validator.validate(
            policies3,
            TimePartitionSpec.builder()
                .columnName("ts")
                .granularity(TimePartitionSpec.Granularity.DAY)
                .build(),
            TableUri.builder().build(),
            getSchemaJsonFromSchema(dummySchema)));

    // Negative: Having both timepartitionspec AND invalid-pattern
    RetentionColumnPattern malformedPattern =
        RetentionColumnPattern.builder().pattern("random_pattern").columnName("aa").build();
    Retention retention4 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.DAY)
            .columnPattern(malformedPattern)
            .build();
    Policies policies4 = Policies.builder().retention(retention4).build();
    Assertions.assertFalse(
        validator.validate(
            policies4,
            TimePartitionSpec.builder()
                .columnName("ts")
                .granularity(TimePartitionSpec.Granularity.DAY)
                .build(),
            TableUri.builder().build(),
            getSchemaJsonFromSchema(dummySchema)));

    Field failedMsg =
        org.springframework.util.ReflectionUtils.findField(
            PoliciesSpecValidator.class, "failureMessage");
    Assertions.assertNotNull(failedMsg);
    org.springframework.util.ReflectionUtils.makeAccessible(failedMsg);
    Assertions.assertTrue(
        ((String) org.springframework.util.ReflectionUtils.getField(failedMsg, validator))
            .contains("You can only specify retention column pattern on non-timestampPartitioned"));

    // Negative: having granularity not supported by defaultColumPattern
    RetentionColumnPattern defaultPattern =
        RetentionColumnPattern.builder().columnName("aa").pattern("").build();
    Retention retention5 =
        Retention.builder()
            .count(1)
            .granularity(TimePartitionSpec.Granularity.MONTH)
            .columnPattern(defaultPattern)
            .build();
    Policies policies5 = Policies.builder().retention(retention5).build();
    Assertions.assertFalse(
        validator.validate(
            policies5, null, TableUri.builder().build(), getSchemaJsonFromSchema(dummySchema)));

    failedMsg =
        org.springframework.util.ReflectionUtils.findField(
            PoliciesSpecValidator.class, "failureMessage");
    Assertions.assertNotNull(failedMsg);
    org.springframework.util.ReflectionUtils.makeAccessible(failedMsg);
    Assertions.assertTrue(
        ((String) org.springframework.util.ReflectionUtils.getField(failedMsg, validator))
            .contains("Please define pattern in retention config"));

    // The granularity mismatch is covered in
    // com.linkedin.openhouse.tables.e2e.h2.TablesControllerTest.testCreateRequestFailsForWithGranularityDifferentFromTimePartitionSpec
    // with error message validation

  }