diff --git a/CHANGELOG.md b/CHANGELOG.md
index d75bc38..26d29ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
+## [0.0.1-RC7]
+
+- Introduced a `leia-common` module to host all the common utils classes
+- Added annotation classes for the Qualifiers( PII, Encrypted, ShortLived) that can be added on the members of
+ the Schema class
+- Replaced the `SchemaValidatable` annotation and moved it to a generic `SchemaDefinition`
+- Introduced `SchemaBuilder`: For building the schema request against a class annotated with SchemaDefinition
+- Introduced `SchemaPayloadValidator`: For validating a schema json payload against a specified SchemaKey
+- Addressed issues in handling plain `Object` and Primitive Class types in `SchemaValidationUtils`
+
## [0.0.1-RC6]
- LeiaClient - Introduced AuthHeaderSupplier in LeiaClientSupplier to support authentication on leia-server
diff --git a/README.md b/README.md
index 578c76c..661a4d8 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Leia is a governance and metadata framework aimed at meeting compliance requirem
com.grookage.leia
leia-bom
- 0.0.1-RC6
+ 0.0.1-RC7
```
@@ -92,9 +92,11 @@ A sample schema looks like the following
"type": "ARRAY",
"name": "testAttribute",
"optional": true,
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
},
{
"type": "ENUM",
@@ -103,9 +105,11 @@ A sample schema looks like the following
"values": [
"TEST_ENUM"
],
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
}
],
"transformationTargets": [
@@ -132,6 +136,8 @@ A sample schema looks like the following
the `LeiaMessageProduceClient`, will multiplex the testSchema to both the versions, the transformationTargets ought to
be jsonPathRules.
+Please refer to the `SchemaBuilder` class which can be used to generate schema against a class.
+
#### Using the LeiaClientBundle
On the client side, please use the `LeiaClientBundle`
diff --git a/leia-bom/pom.xml b/leia-bom/pom.xml
index a8f5330..5897097 100644
--- a/leia-bom/pom.xml
+++ b/leia-bom/pom.xml
@@ -21,7 +21,7 @@
com.grookage.leia
leia
- 0.0.1-RC6
+ 0.0.1-RC7
leia-bom
@@ -39,6 +39,11 @@
leia-models
${project.version}
+
+ com.grookage.leia
+ leia-common
+ ${project.version}
+
com.grookage.leia
leia-core
diff --git a/leia-client-dropwizard/pom.xml b/leia-client-dropwizard/pom.xml
index b0e5970..c722706 100644
--- a/leia-client-dropwizard/pom.xml
+++ b/leia-client-dropwizard/pom.xml
@@ -22,7 +22,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-client/pom.xml b/leia-client/pom.xml
index 8217c29..d1f72eb 100644
--- a/leia-client/pom.xml
+++ b/leia-client/pom.xml
@@ -22,13 +22,14 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
leia-client
+ 3.11
false
@@ -73,6 +74,11 @@
leia-models
+
+ com.grookage.leia
+ leia-common
+
+
com.grookage.leia
leia-schema-validator
diff --git a/leia-common/pom.xml b/leia-common/pom.xml
new file mode 100644
index 0000000..e88605f
--- /dev/null
+++ b/leia-common/pom.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+ 4.0.0
+
+ com.grookage.leia
+ leia-parent
+ 0.0.1-RC7
+ ../leia-parent
+
+
+ leia-common
+
+
+ 3.11
+ false
+
+
+
+
+ lombok
+ org.projectlombok
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${lang3.version}
+
+
+
+ junit-jupiter
+ org.junit.jupiter
+
+
+
+ com.grookage.leia
+ leia-models
+
+
+
+ mockito-core
+ org.mockito
+ test
+
+
+
+ mockito-junit-jupiter
+ org.mockito
+ test
+
+
+
+ mockito-inline
+ org.mockito
+ test
+
+
+
+ com.grookage.leia
+ leia-models
+ test-jar
+
+
+ *
+ *
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java
new file mode 100644
index 0000000..5a87de2
--- /dev/null
+++ b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2024. Koushik R .
+ *
+ * Licensed 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 com.grookage.leia.common.builder;
+
+import com.grookage.leia.common.utils.FieldUtils;
+import com.grookage.leia.common.utils.QualifierUtils;
+import com.grookage.leia.models.annotations.SchemaDefinition;
+import com.grookage.leia.models.attributes.ArrayAttribute;
+import com.grookage.leia.models.attributes.BooleanAttribute;
+import com.grookage.leia.models.attributes.DoubleAttribute;
+import com.grookage.leia.models.attributes.EnumAttribute;
+import com.grookage.leia.models.attributes.FloatAttribute;
+import com.grookage.leia.models.attributes.IntegerAttribute;
+import com.grookage.leia.models.attributes.LongAttribute;
+import com.grookage.leia.models.attributes.MapAttribute;
+import com.grookage.leia.models.attributes.ObjectAttribute;
+import com.grookage.leia.models.attributes.SchemaAttribute;
+import com.grookage.leia.models.attributes.StringAttribute;
+import com.grookage.leia.models.qualifiers.QualifierInfo;
+import com.grookage.leia.models.schema.ingestion.CreateSchemaRequest;
+import lombok.experimental.UtilityClass;
+import org.apache.commons.lang3.ClassUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@UtilityClass
+public class SchemaBuilder {
+ public Optional buildSchemaRequest(final Class> klass) {
+ if (Objects.isNull(klass) || !klass.isAnnotationPresent(SchemaDefinition.class)) {
+ return Optional.empty();
+ }
+ final var schemaDefinition = klass.getAnnotation(SchemaDefinition.class);
+ return Optional.of(CreateSchemaRequest.builder()
+ .schemaName(schemaDefinition.name())
+ .namespace(schemaDefinition.namespace())
+ .description(schemaDefinition.description())
+ .schemaType(schemaDefinition.type())
+ .validationType(schemaDefinition.validation())
+ .attributes(getSchemaAttributes(klass))
+ .build()
+ );
+ }
+
+ public Set getSchemaAttributes(final Class> klass) {
+ return FieldUtils.getAllFields(klass)
+ .stream()
+ .map(SchemaBuilder::schemaAttribute)
+ .collect(Collectors.toSet());
+ }
+
+ private SchemaAttribute schemaAttribute(final Field field) {
+ return schemaAttribute(
+ field.getGenericType(),
+ field.getName(),
+ QualifierUtils.getQualifiers(field),
+ isOptional(field)
+ );
+ }
+
+ private SchemaAttribute schemaAttribute(final Type type,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.)
+ if (type instanceof Class> klass) {
+ return schemaAttribute(klass, name, qualifiers, optional);
+ }
+
+ // Handle ParameterizedType (e.g., List, Map)
+ if (type instanceof ParameterizedType parameterizedType) {
+ return schemaAttribute(parameterizedType, name, qualifiers, optional);
+ }
+
+ // Handle GenericArrayType (e.g., T[], List)
+ if (type instanceof GenericArrayType genericArrayType) {
+ return schemaAttribute(genericArrayType, name, qualifiers, optional);
+ }
+
+ throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName());
+ }
+
+ private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ final var rawType = (Class>) parameterizedType.getRawType();
+ // Handle List or Set
+ if (ClassUtils.isAssignable(rawType, Collection.class)) {
+ return handleCollection(parameterizedType, name, qualifiers, optional);
+ }
+
+ // Handle Map
+ if (ClassUtils.isAssignable(rawType, Map.class)) {
+ return handleMap(parameterizedType, name, qualifiers, optional);
+ }
+ throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName());
+ }
+
+ private SchemaAttribute handleMap(final ParameterizedType parameterizedType,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ final var keyType = parameterizedType.getActualTypeArguments()[0];
+ final var valueType = parameterizedType.getActualTypeArguments()[1];
+ return new MapAttribute(
+ name,
+ optional,
+ qualifiers,
+ schemaAttribute(keyType, "key", QualifierUtils.getQualifiers(keyType), isOptional(keyType)),
+ schemaAttribute(valueType, "value", QualifierUtils.getQualifiers(valueType), isOptional(valueType))
+ );
+ }
+
+ private SchemaAttribute handleCollection(final ParameterizedType parameterizedType,
+ final String name,
+ final Set qualifiers, boolean optional) {
+ final var elementType = parameterizedType.getActualTypeArguments()[0];
+ return new ArrayAttribute(
+ name,
+ optional,
+ qualifiers,
+ schemaAttribute(elementType, "element", QualifierUtils.getQualifiers(elementType),
+ isOptional(elementType))
+ );
+ }
+
+ private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ final var componentType = genericArrayType.getGenericComponentType();
+ return new ArrayAttribute(
+ name,
+ optional,
+ qualifiers,
+ schemaAttribute(componentType, "element", QualifierUtils.getQualifiers(componentType),
+ isOptional(componentType))
+ );
+ }
+
+
+ private SchemaAttribute schemaAttribute(final Class> klass,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ if (klass == String.class) {
+ return new StringAttribute(name, optional, qualifiers);
+ }
+
+ if (klass.isEnum()) {
+ return new EnumAttribute(name, optional, qualifiers, getEnumValues(klass));
+ }
+
+ // Handle int, long, boolean etc.
+ if (klass.isPrimitive()) {
+ return handlePrimitive(klass, name, qualifiers, optional);
+ }
+
+ // Handle String[], Object[] etc.
+ if (klass.isArray()) {
+ final var componentType = klass.getComponentType();
+ return new ArrayAttribute(
+ name,
+ optional,
+ qualifiers,
+ schemaAttribute(componentType, "element", QualifierUtils.getQualifiers(componentType),
+ isOptional(componentType))
+ );
+ }
+
+ // Handle Raw List, Set
+ if (ClassUtils.isAssignable(klass, Collection.class)) {
+ return new ArrayAttribute(name, optional, qualifiers, null);
+ }
+
+ // Handle Raw Map
+ if (ClassUtils.isAssignable(klass, Map.class)) {
+ return new MapAttribute(name, optional, qualifiers, null, null);
+ }
+
+ if (klass.equals(Object.class)) {
+ return new ObjectAttribute(name, optional, qualifiers, null);
+ }
+
+ // Handling custom defined POJO's
+ final var schemaAttributes = getSchemaAttributes(klass);
+ return new ObjectAttribute(name, optional, qualifiers, schemaAttributes);
+ }
+
+ private SchemaAttribute handlePrimitive(final Class> klass,
+ final String name,
+ final Set qualifiers,
+ final boolean optional) {
+ if (klass == Integer.class || klass == int.class) {
+ return new IntegerAttribute(name, optional, qualifiers);
+ }
+ if (klass == Boolean.class || klass == boolean.class) {
+ return new BooleanAttribute(name, optional, qualifiers);
+ }
+ if (klass == Double.class || klass == double.class) {
+ return new DoubleAttribute(name, optional, qualifiers);
+ }
+ if (klass == Long.class || klass == long.class) {
+ return new LongAttribute(name, optional, qualifiers);
+ }
+ if (klass == Float.class || klass == float.class) {
+ return new FloatAttribute(name, optional, qualifiers);
+ }
+
+ throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName());
+
+ }
+
+ private Set getEnumValues(final Class> klass) {
+ return Arrays.stream(klass.getEnumConstants())
+ .map(enumConstant -> ((Enum>) enumConstant).name())
+ .collect(Collectors.toSet());
+ }
+
+ private boolean isOptional(final Type type) {
+ if (type instanceof Class> klass) {
+ return isOptional(klass);
+ }
+ return false;
+ }
+
+ private boolean isOptional(final Class> klass) {
+ return klass.isAnnotationPresent(com.grookage.leia.models.annotations.attribute.Optional.class);
+ }
+
+ private boolean isOptional(final Field field) {
+ return field.isAnnotationPresent(com.grookage.leia.models.annotations.attribute.Optional.class);
+ }
+}
diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java b/leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java
similarity index 98%
rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java
rename to leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java
index 99667f3..20b8aca 100644
--- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java
+++ b/leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.grookage.leia.validator.exception;
+package com.grookage.leia.common.exception;
import lombok.Builder;
import lombok.Getter;
diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java b/leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java
similarity index 94%
rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java
rename to leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java
index 03284aa..fff4e56 100644
--- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java
+++ b/leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.grookage.leia.validator.exception;
+package com.grookage.leia.common.exception;
import lombok.Getter;
diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java b/leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java
similarity index 55%
rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java
rename to leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java
index fd19e09..c60c815 100644
--- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java
+++ b/leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java
@@ -14,21 +14,22 @@
* limitations under the License.
*/
-package com.grookage.leia.validator.annotations;
+package com.grookage.leia.common.utils;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import lombok.experimental.UtilityClass;
-@Target({ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface SchemaValidatable {
-
- String schemaName();
-
- String versionId();
-
- String namespace();
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+@UtilityClass
+public class FieldUtils {
+ public List getAllFields(final Class> type) {
+ List fields = new ArrayList<>();
+ for (Class> c = type; c != null; c = c.getSuperclass()) {
+ fields.addAll(Arrays.asList(c.getDeclaredFields()));
+ }
+ return fields;
+ }
}
diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java
new file mode 100644
index 0000000..6e8e3ee
--- /dev/null
+++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2024. Koushik R .
+ *
+ * Licensed 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 com.grookage.leia.common.utils;
+
+import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted;
+import com.grookage.leia.models.annotations.attribute.qualifiers.PII;
+import com.grookage.leia.models.annotations.attribute.qualifiers.ShortLived;
+import com.grookage.leia.models.qualifiers.EncryptedQualifier;
+import com.grookage.leia.models.qualifiers.PIIQualifier;
+import com.grookage.leia.models.qualifiers.QualifierInfo;
+import com.grookage.leia.models.qualifiers.QualifierType;
+import com.grookage.leia.models.qualifiers.ShortLivedQualifier;
+import lombok.experimental.UtilityClass;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+@UtilityClass
+public class QualifierUtils {
+ public Optional filter(final Set qualifiers,
+ final QualifierType type) {
+ if (Objects.isNull(qualifiers)) {
+ return Optional.empty();
+ }
+ return qualifiers.stream()
+ .filter(qualifierInfo -> qualifierInfo.getType().equals(type))
+ .findFirst();
+ }
+
+ public Set getQualifiers(final Type type) {
+ if (type instanceof Class> klass) {
+ return getQualifiers(klass);
+ }
+ return new HashSet<>();
+ }
+
+ public Set getQualifiers(final Field field) {
+ Set qualifiers = new HashSet<>();
+ if (field.isAnnotationPresent(Encrypted.class)) {
+ qualifiers.add(new EncryptedQualifier());
+ }
+ if (field.isAnnotationPresent(PII.class)) {
+ qualifiers.add(new PIIQualifier());
+ }
+ if (field.isAnnotationPresent(ShortLived.class)) {
+ final var shortLived = field.getAnnotation(ShortLived.class);
+ qualifiers.add(new ShortLivedQualifier(shortLived.ttlSeconds()));
+ }
+ return qualifiers;
+ }
+
+ public Set getQualifiers(final Class> klass) {
+ Set qualifiers = new HashSet<>();
+ if (klass.isAnnotationPresent(Encrypted.class)) {
+ qualifiers.add(new EncryptedQualifier());
+ }
+ if (klass.isAnnotationPresent(PII.class)) {
+ qualifiers.add(new PIIQualifier());
+ }
+ if (klass.isAnnotationPresent(ShortLived.class)) {
+ final var shortLived = klass.getAnnotation(ShortLived.class);
+ qualifiers.add(new ShortLivedQualifier(shortLived.ttlSeconds()));
+ }
+ return qualifiers;
+ }
+}
diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java
similarity index 69%
rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java
rename to leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java
index 076b170..0afba08 100644
--- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java
+++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java
@@ -14,23 +14,31 @@
* limitations under the License.
*/
-package com.grookage.leia.validator.utils;
+package com.grookage.leia.common.utils;
import com.google.common.collect.Sets;
-import com.grookage.leia.models.attributes.*;
+import com.grookage.leia.common.exception.SchemaValidationException;
+import com.grookage.leia.common.exception.ValidationErrorCode;
+import com.grookage.leia.models.attributes.ArrayAttribute;
+import com.grookage.leia.models.attributes.MapAttribute;
+import com.grookage.leia.models.attributes.ObjectAttribute;
+import com.grookage.leia.models.attributes.SchemaAttribute;
+import com.grookage.leia.models.attributes.SchemaAttributeHandler;
import com.grookage.leia.models.schema.SchemaDetails;
import com.grookage.leia.models.schema.SchemaValidationType;
import com.grookage.leia.models.schema.SchemaValidationVisitor;
-import com.grookage.leia.validator.exception.SchemaValidationException;
-import com.grookage.leia.validator.exception.ValidationErrorCode;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ClassUtils;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -38,7 +46,7 @@
@Slf4j
public class SchemaValidationUtils {
static Function, Function> assignableCheckFunction =
- klass -> attribute -> attribute.getType().getAssignableClass().isAssignableFrom(klass);
+ klass -> attribute -> ClassUtils.isAssignable(klass, attribute.getType().getAssignableClass());
static Function throwException = attribute -> {
log.error("Attribute {} of type {} not compatible with the type provided",
@@ -46,15 +54,16 @@ public class SchemaValidationUtils {
throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS);
};
- public static boolean valid(final SchemaDetails schemaDetails,
- final Class> klass) {
+ public boolean valid(final SchemaDetails schemaDetails,
+ final Class> klass) {
return valid(schemaDetails.getValidationType(), schemaDetails.getAttributes(), klass);
}
- public static boolean valid(final SchemaValidationType validationType,
- Set attributes, final Class> klass) {
+ public boolean valid(final SchemaValidationType validationType,
+ final Set attributes,
+ final Class> klass) {
- final var fields = getAllFields(klass);
+ final var fields = FieldUtils.getAllFields(klass);
if (!validSchema(validationType, attributes, fields)) {
return false;
}
@@ -62,8 +71,9 @@ public static boolean valid(final SchemaValidationType validationType,
each -> validAttribute(each, fields, validationType));
}
- private static boolean validSchema(SchemaValidationType validationType, Set attributes,
- List fields) {
+ private boolean validSchema(final SchemaValidationType validationType,
+ final Set attributes,
+ final List fields) {
final var fieldNames = fields.stream()
.map(Field::getName)
.map(String::toUpperCase)
@@ -98,24 +108,18 @@ public Boolean matching() {
});
}
- private static List getAllFields(Class> type) {
- List fields = new ArrayList<>();
- for (Class> c = type; c != null; c = c.getSuperclass()) {
- fields.addAll(Arrays.asList(c.getDeclaredFields()));
- }
- return fields;
- }
-
- private static boolean validAttribute(final SchemaAttribute attribute,
- List fields, SchemaValidationType validationType) {
+ private boolean validAttribute(final SchemaAttribute attribute,
+ final List fields,
+ final SchemaValidationType validationType) {
final var field = fields.stream()
.filter(each -> each.getName().equals(attribute.getName()))
.findFirst().orElse(null);
return null != field && valid(validationType, attribute, field.getGenericType());
}
- public static boolean valid(final SchemaValidationType validationType,
- SchemaAttribute attribute, final Type type) {
+ public boolean valid(final SchemaValidationType validationType,
+ final SchemaAttribute attribute,
+ final Type type) {
if (type instanceof Class> klass) {
return valid(validationType, attribute, klass);
} else if (type instanceof ParameterizedType parameterizedType) {
@@ -127,8 +131,9 @@ public static boolean valid(final SchemaValidationType validationType,
}
}
- private static boolean valid(final SchemaValidationType validationType,
- SchemaAttribute attribute, final Class> klass) {
+ private boolean valid(final SchemaValidationType validationType,
+ final SchemaAttribute attribute,
+ final Class> klass) {
return attribute.accept(new SchemaAttributeHandler<>(
assignableCheckFunction.apply(klass)) {
@Override
@@ -139,23 +144,28 @@ public Boolean accept(ArrayAttribute attribute) {
}
return valid(validationType, attribute.getElementAttribute(), klass.getComponentType());
}
- return Collection.class.isAssignableFrom(klass) && attribute.getElementAttribute() == null;
+ return ClassUtils.isAssignable(klass, Collection.class) && attribute.getElementAttribute() == null;
}
@Override
public Boolean accept(MapAttribute attribute) {
- return Map.class.isAssignableFrom(klass) && attribute.getKeyAttribute() == null;
+ return ClassUtils.isAssignable(klass, Map.class) && attribute.getKeyAttribute() == null;
}
@Override
public Boolean accept(ObjectAttribute attribute) {
+ // Handling plain Object.class
+ if (klass.equals(Object.class) && attribute.getNestedAttributes() == null) {
+ return true;
+ }
return valid(validationType, attribute.getNestedAttributes(), klass);
}
});
}
- private static boolean valid(final SchemaValidationType validationType,
- SchemaAttribute attribute, final ParameterizedType parameterizedType) {
+ private boolean valid(final SchemaValidationType validationType,
+ final SchemaAttribute attribute,
+ final ParameterizedType parameterizedType) {
return attribute.accept(new SchemaAttributeHandler<>(throwException) {
@Override
public Boolean accept(ArrayAttribute attribute) {
@@ -163,7 +173,7 @@ public Boolean accept(ArrayAttribute attribute) {
return true;
}
final var rawType = (Class>) parameterizedType.getRawType();
- if (!attribute.getType().getAssignableClass().isAssignableFrom(rawType)) {
+ if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) {
return false;
}
final var typeArguments = getTypeArguments(parameterizedType);
@@ -176,7 +186,7 @@ public Boolean accept(MapAttribute attribute) {
return true;
}
final var rawType = (Class>) parameterizedType.getRawType();
- if (!attribute.getType().getAssignableClass().isAssignableFrom(rawType)) {
+ if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) {
return false;
}
final var typeArguments = getTypeArguments(parameterizedType);
@@ -186,8 +196,18 @@ public Boolean accept(MapAttribute attribute) {
});
}
- private static boolean valid(final SchemaValidationType validationType,
- SchemaAttribute attribute, final GenericArrayType arrayType) {
+ private Type[] getTypeArguments(final ParameterizedType parameterizedType) {
+ final var typeArguments = parameterizedType.getActualTypeArguments();
+ if (typeArguments.length == 0) {
+ throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS,
+ String.format("No type arguments found for %s", parameterizedType));
+ }
+ return typeArguments;
+ }
+
+ private boolean valid(final SchemaValidationType validationType,
+ final SchemaAttribute attribute,
+ final GenericArrayType arrayType) {
return attribute.accept(new SchemaAttributeHandler<>(throwException) {
@Override
public Boolean accept(final ArrayAttribute attribute) {
@@ -196,16 +216,8 @@ public Boolean accept(final ArrayAttribute attribute) {
});
}
- private static Type[] getTypeArguments(ParameterizedType parameterizedType) {
- Type[] typeArguments = parameterizedType.getActualTypeArguments();
- if (typeArguments.length == 0) {
- throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS,
- String.format("No type arguments found for %s", parameterizedType));
- }
- return typeArguments;
- }
-
- public static boolean valid(Class> klass, SchemaAttribute schemaAttribute) {
+ public boolean valid(final Class> klass,
+ final SchemaAttribute schemaAttribute) {
return schemaAttribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) {
});
}
diff --git a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java
new file mode 100644
index 0000000..f12cf98
--- /dev/null
+++ b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2024. Koushik R .
+ *
+ * Licensed 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 com.grookage.leia.common.validation;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.grookage.leia.models.attributes.ArrayAttribute;
+import com.grookage.leia.models.attributes.BooleanAttribute;
+import com.grookage.leia.models.attributes.ByteAttribute;
+import com.grookage.leia.models.attributes.DoubleAttribute;
+import com.grookage.leia.models.attributes.EnumAttribute;
+import com.grookage.leia.models.attributes.FloatAttribute;
+import com.grookage.leia.models.attributes.IntegerAttribute;
+import com.grookage.leia.models.attributes.LongAttribute;
+import com.grookage.leia.models.attributes.MapAttribute;
+import com.grookage.leia.models.attributes.ObjectAttribute;
+import com.grookage.leia.models.attributes.SchemaAttribute;
+import com.grookage.leia.models.attributes.SchemaAttributeAcceptor;
+import com.grookage.leia.models.attributes.StringAttribute;
+import com.grookage.leia.models.schema.SchemaValidationType;
+import com.grookage.leia.models.utils.MapperUtils;
+import lombok.experimental.UtilityClass;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@UtilityClass
+public class SchemaPayloadValidator {
+ public static List validate(final JsonNode jsonNode,
+ final SchemaValidationType validationType,
+ final Set schemaAttributes) {
+ List validationErrors = new ArrayList<>();
+
+ Map schemaMap = new HashMap<>();
+ for (SchemaAttribute attribute : schemaAttributes) {
+ schemaMap.put(attribute.getName(), attribute);
+ }
+
+ // Validate extra fields in case of Strict Validation
+ if (validationType == SchemaValidationType.STRICT) {
+ jsonNode.fieldNames().forEachRemaining(fieldName -> {
+ if (!schemaMap.containsKey(fieldName)) {
+ validationErrors.add("Unexpected field: " + fieldName);
+ }
+ });
+ }
+
+ // Validate missing and type mismatched fields
+ for (SchemaAttribute attribute : schemaAttributes) {
+ final var fieldName = attribute.getName();
+ // Check the attribute only if the jsonNode is an object
+ if (jsonNode.isObject() && !jsonNode.has(fieldName)) {
+ if (!attribute.isOptional()) {
+ validationErrors.add("Missing required field: " + fieldName);
+ }
+ continue;
+ }
+
+ if (jsonNode.isValueNode()) {
+ validateField(jsonNode, attribute, validationType, validationErrors);
+ continue;
+ }
+ final var fieldNode = jsonNode.get(fieldName);
+ validateField(fieldNode, attribute, validationType, validationErrors);
+ }
+
+ return validationErrors;
+ }
+
+ private void validateField(final JsonNode fieldNode,
+ final SchemaAttribute attribute,
+ final SchemaValidationType validationType,
+ final List validationErrors) {
+ final var fieldName = attribute.getName();
+ if (!isMatchingType(fieldNode, attribute)) {
+ validationErrors.add("Type mismatch for field: " + fieldName +
+ ". Expected: " + attribute.getType() +
+ ", Found: " + fieldNode.getNodeType());
+ return;
+ }
+
+ // Recursively validate nested objects
+ if (attribute instanceof ObjectAttribute objectAttribute) {
+ if (objectAttribute.getNestedAttributes() != null) {
+ validationErrors.addAll(validate(fieldNode, validationType, objectAttribute.getNestedAttributes()));
+ }
+ return;
+ }
+ if (attribute instanceof ArrayAttribute arrayAttribute) {
+ validateCollectionAttribute(fieldNode, arrayAttribute, validationType, validationErrors);
+ }
+ if (attribute instanceof MapAttribute mapAttribute) {
+ validateMapAttribute(fieldNode, mapAttribute, validationType, validationErrors);
+ }
+ }
+
+ private void validateCollectionAttribute(final JsonNode fieldNode,
+ final ArrayAttribute arrayAttribute,
+ final SchemaValidationType schemaValidationType,
+ final List validationErrors) {
+ // Handling Non-Parameterized Collections (eg: List.class, Set.class, Map.class etc.)
+ if (arrayAttribute.getElementAttribute() == null) {
+ return;
+ }
+
+ for (JsonNode arrayElement : fieldNode) {
+ validateField(arrayElement, arrayAttribute.getElementAttribute(), schemaValidationType, validationErrors);
+ }
+ }
+
+ private void validateMapAttribute(final JsonNode fieldNode,
+ final MapAttribute mapAttribute,
+ final SchemaValidationType schemaValidationType,
+ final List validationErrors) {
+ // Handling Raw Map.class
+ if (Objects.isNull(mapAttribute.getKeyAttribute()) && Objects.isNull(mapAttribute.getValueAttribute())) {
+ return;
+ }
+ fieldNode.fields().forEachRemaining(entry -> {
+ final var keyNode = entry.getKey() != null
+ ? MapperUtils.mapper().convertValue(entry.getKey(), JsonNode.class)
+ : null;
+ if (!Objects.isNull(keyNode)) {
+ // validate Key
+ validateField(keyNode, mapAttribute.getKeyAttribute(), schemaValidationType, validationErrors);
+ // Validate value
+ validateField(entry.getValue(), mapAttribute.getValueAttribute(), schemaValidationType, validationErrors);
+ } else {
+ validationErrors.add("Key Not present for map attribute field:" +
+ mapAttribute.getName());
+ }
+ });
+
+ }
+
+ private boolean isMatchingType(final JsonNode fieldNode,
+ final SchemaAttribute attribute) {
+ return attribute.accept(new SchemaAttributeAcceptor<>() {
+ @Override
+ public Boolean accept(BooleanAttribute attribute) {
+ return fieldNode.isBoolean();
+ }
+
+ @Override
+ public Boolean accept(ByteAttribute attribute) {
+ return fieldNode.isArray();
+ }
+
+ @Override
+ public Boolean accept(DoubleAttribute attribute) {
+ return fieldNode.isDouble() || fieldNode.isFloat() || fieldNode.isInt();
+ }
+
+ @Override
+ public Boolean accept(EnumAttribute attribute) {
+ return fieldNode.isTextual() && attribute.getValues().contains(fieldNode.asText());
+ }
+
+ @Override
+ public Boolean accept(FloatAttribute attribute) {
+ return fieldNode.isFloat();
+ }
+
+ @Override
+ public Boolean accept(IntegerAttribute attribute) {
+ return fieldNode.isInt();
+ }
+
+ @Override
+ public Boolean accept(LongAttribute attribute) {
+ return fieldNode.isLong() || fieldNode.isInt();
+ }
+
+ @Override
+ public Boolean accept(StringAttribute attribute) {
+ return fieldNode.isTextual();
+ }
+
+ @Override
+ public Boolean accept(ArrayAttribute attribute) {
+ return fieldNode.isArray();
+ }
+
+ @Override
+ public Boolean accept(MapAttribute attribute) {
+ return fieldNode.isObject();
+ }
+
+ @Override
+ public Boolean accept(ObjectAttribute attribute) {
+ // Handling Object.class
+ if (attribute.getNestedAttributes() == null) {
+ return true;
+ }
+ return fieldNode.isObject();
+ }
+ });
+ }
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java b/leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java
new file mode 100644
index 0000000..331e35b
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java
@@ -0,0 +1,164 @@
+package com.grookage.leia.common;
+
+import com.grookage.leia.common.utils.QualifierUtils;
+import com.grookage.leia.models.attributes.ArrayAttribute;
+import com.grookage.leia.models.attributes.BooleanAttribute;
+import com.grookage.leia.models.attributes.ByteAttribute;
+import com.grookage.leia.models.attributes.DoubleAttribute;
+import com.grookage.leia.models.attributes.EnumAttribute;
+import com.grookage.leia.models.attributes.FloatAttribute;
+import com.grookage.leia.models.attributes.IntegerAttribute;
+import com.grookage.leia.models.attributes.LongAttribute;
+import com.grookage.leia.models.attributes.MapAttribute;
+import com.grookage.leia.models.attributes.ObjectAttribute;
+import com.grookage.leia.models.attributes.SchemaAttribute;
+import com.grookage.leia.models.attributes.SchemaAttributeAcceptor;
+import com.grookage.leia.models.attributes.StringAttribute;
+import com.grookage.leia.models.qualifiers.QualifierInfo;
+import com.grookage.leia.models.qualifiers.QualifierType;
+import com.grookage.leia.models.qualifiers.ShortLivedQualifier;
+import lombok.experimental.UtilityClass;
+import org.junit.jupiter.api.Assertions;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+@UtilityClass
+public class LeiaTestUtils {
+ public Optional filter(Set schemaAttributes,
+ String name) {
+ return schemaAttributes.stream()
+ .filter(schemaAttribute -> schemaAttribute.getName().equals(name))
+ .findFirst();
+ }
+
+ public void assertEquals(SchemaAttribute expected,
+ SchemaAttribute original) {
+ if (Objects.isNull(expected) && Objects.isNull(original)) {
+ return;
+ }
+ Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch");
+ Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch");
+ Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch");
+
+ // Compare QualifierInfo
+ assertEquals(expected.getQualifiers(), original.getQualifiers());
+
+ // Accept the expected attribute type and perform specific validations
+ expected.accept(new SchemaAttributeAcceptor() {
+ @Override
+ public Void accept(BooleanAttribute attribute) {
+ Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(ByteAttribute attribute) {
+ Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(DoubleAttribute attribute) {
+ Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(EnumAttribute attribute) {
+ Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute");
+ EnumAttribute originalEnum = (EnumAttribute) original;
+ Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch");
+ return null;
+ }
+
+ @Override
+ public Void accept(FloatAttribute attribute) {
+ Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(IntegerAttribute attribute) {
+ Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(LongAttribute attribute) {
+ Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(StringAttribute attribute) {
+ Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute");
+ return null;
+ }
+
+ @Override
+ public Void accept(ArrayAttribute attribute) {
+ Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute");
+ ArrayAttribute originalArray = (ArrayAttribute) original;
+ assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute
+ return null;
+ }
+
+ @Override
+ public Void accept(MapAttribute attribute) {
+ Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute");
+ MapAttribute originalMap = (MapAttribute) original;
+ assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key
+ assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value
+ return null;
+ }
+
+ @Override
+ public Void accept(ObjectAttribute attribute) {
+ Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute");
+ ObjectAttribute originalObject = (ObjectAttribute) original;
+
+ if (Objects.isNull(attribute.getNestedAttributes()) && Objects.isNull(originalObject.getNestedAttributes())) {
+ return null;
+ }
+
+ // Recursive comparison of nested attributes
+ Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(),
+ "Nested attributes size mismatch");
+ Iterator expectedIterator = attribute.getNestedAttributes().iterator();
+ Iterator originalIterator = originalObject.getNestedAttributes().iterator();
+
+ while (expectedIterator.hasNext() && originalIterator.hasNext()) {
+ assertEquals(expectedIterator.next(), originalIterator.next());
+ }
+ return null;
+ }
+ });
+ }
+
+ public void assertEquals(Set expected,
+ Set original) {
+ Assertions.assertNotNull(expected, "Expected qualifiers should not be null");
+ Assertions.assertNotNull(original, "Actual qualifiers should not be null");
+ Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch");
+
+ expected.forEach(expectedQualifier -> {
+ Optional matchingQualifier = QualifierUtils.filter(original, expectedQualifier.getType());
+
+ Assertions.assertTrue(matchingQualifier.isPresent(),
+ "Missing qualifier of type: " + expectedQualifier.getType());
+
+ if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) {
+ Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier");
+
+ ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier;
+ ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get();
+
+ Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(),
+ "Mismatch in TTL seconds for SHORT_LIVED qualifier");
+ }
+ });
+ }
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java
new file mode 100644
index 0000000..3fb5d8f
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java
@@ -0,0 +1,221 @@
+package com.grookage.leia.common.builder;
+
+import com.grookage.leia.common.LeiaTestUtils;
+import com.grookage.leia.common.stubs.NestedStub;
+import com.grookage.leia.common.stubs.RecordStub;
+import com.grookage.leia.common.stubs.TestEnum;
+import com.grookage.leia.common.stubs.TestObjectStub;
+import com.grookage.leia.common.stubs.TestParameterizedStub;
+import com.grookage.leia.common.stubs.TestRawCollectionStub;
+import com.grookage.leia.models.annotations.SchemaDefinition;
+import com.grookage.leia.models.annotations.attribute.Optional;
+import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted;
+import com.grookage.leia.models.annotations.attribute.qualifiers.PII;
+import com.grookage.leia.models.attributes.ArrayAttribute;
+import com.grookage.leia.models.attributes.EnumAttribute;
+import com.grookage.leia.models.attributes.IntegerAttribute;
+import com.grookage.leia.models.attributes.MapAttribute;
+import com.grookage.leia.models.attributes.ObjectAttribute;
+import com.grookage.leia.models.attributes.SchemaAttribute;
+import com.grookage.leia.models.attributes.StringAttribute;
+import com.grookage.leia.models.qualifiers.EncryptedQualifier;
+import com.grookage.leia.models.qualifiers.PIIQualifier;
+import com.grookage.leia.models.schema.SchemaType;
+import com.grookage.leia.models.schema.SchemaValidationType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class SchemaBuilderTest {
+ @Test
+ void testSchemaRequest() {
+ final var schemaCreateRequest = SchemaBuilder.buildSchemaRequest(TestRecord.class)
+ .orElse(null);
+ Assertions.assertNotNull(schemaCreateRequest);
+ final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestRecord.class);
+ Assertions.assertEquals(TestRecord.NAME, schemaCreateRequest.getSchemaName());
+ Assertions.assertEquals(TestRecord.NAMESPACE, schemaCreateRequest.getNamespace());
+ Assertions.assertEquals(TestRecord.DESCRIPTION, schemaCreateRequest.getDescription());
+ Assertions.assertEquals(SchemaType.JSON, schemaCreateRequest.getSchemaType());
+ Assertions.assertEquals(SchemaValidationType.MATCHING, schemaCreateRequest.getValidationType());
+ Assertions.assertEquals(schemaAttributes.size(), schemaCreateRequest.getAttributes().size());
+ }
+
+ @Test
+ void testSchemaRequest_WithInvalidClass() {
+ Assertions.assertTrue(SchemaBuilder.buildSchemaRequest(null).isEmpty());
+ Assertions.assertTrue(SchemaBuilder.buildSchemaRequest(TestObject.class).isEmpty());
+ }
+
+ @Test
+ void testSchemaAttributes_WithPrimitiveClass() {
+ final var schemaAttributeSet = SchemaBuilder.getSchemaAttributes(PrimitiveTestClass.class);
+ Assertions.assertNotNull(schemaAttributeSet);
+ Assertions.assertEquals(2, schemaAttributeSet.size());
+ final var nameAttribute = new StringAttribute("name", true, new HashSet<>());
+ LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributeSet, "name").orElse(null));
+ final var idAttribute = new IntegerAttribute("id", false, new HashSet<>());
+ LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributeSet, "id").orElse(null));
+ }
+
+ @Test
+ void testSchemaAttributes_WithRecordClass() {
+ final var schemaAttributeSet = SchemaBuilder.getSchemaAttributes(RecordStub.class);
+ Assertions.assertNotNull(schemaAttributeSet);
+ Assertions.assertEquals(2, schemaAttributeSet.size());
+ final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier()));
+ LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributeSet, "name").orElse(null));
+ final var idAttribute = new IntegerAttribute("id", true, new HashSet<>());
+ LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributeSet, "id").orElse(null));
+ }
+
+ @Test
+ void testSchemaAttributes_WithNestedObject() {
+ final var schemaAttributes = SchemaBuilder.getSchemaAttributes(NestedStub.class);
+ Assertions.assertFalse(schemaAttributes.isEmpty());
+ Assertions.assertEquals(6, schemaAttributes.size());
+ final var nameAttribute = new StringAttribute("name", false, new HashSet<>());
+ LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributes, "name").orElse(null));
+
+ final var idAttribute = new IntegerAttribute("id", false, new HashSet<>());
+ LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributes, "id").orElse(null));
+
+ final var testPIIDataAttributes = new HashSet();
+ final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>());
+ final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier()));
+ testPIIDataAttributes.add(piiNameAttribute);
+ testPIIDataAttributes.add(accountNumberAttribute);
+ final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes);
+ LeiaTestUtils.assertEquals(piiDataAttribute, LeiaTestUtils.filter(schemaAttributes, "piiData").orElse(null));
+
+ final var testRecordAttributes = new HashSet();
+ final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier()));
+ final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>());
+ testRecordAttributes.add(recordNameAttribute);
+ testRecordAttributes.add(recordIdAttribute);
+ final var testRecordAttribute = new ObjectAttribute("recordStub", false, Set.of(new EncryptedQualifier()),
+ testRecordAttributes);
+ LeiaTestUtils.assertEquals(testRecordAttribute, LeiaTestUtils.filter(schemaAttributes, "recordStub").orElse(null));
+
+ final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(TestEnum.ONE.name(),
+ TestEnum.TWO.name()));
+ LeiaTestUtils.assertEquals(enumClassAttribute, LeiaTestUtils.filter(schemaAttributes, "enumClass").orElse(null));
+
+ final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier()));
+ LeiaTestUtils.assertEquals(phoneNoAttribute, LeiaTestUtils.filter(schemaAttributes, "phoneNumber").orElse(null));
+ }
+
+ @Test
+ void testSchemaAttributes_WithParameterizedType() {
+ final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestParameterizedStub.class);
+ Assertions.assertNotNull(schemaAttributes);
+ Assertions.assertEquals(3, schemaAttributes.size());
+
+ final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(),
+ new StringAttribute("element", false, new HashSet<>()));
+ LeiaTestUtils.assertEquals(valuesAttributes, LeiaTestUtils.filter(schemaAttributes, "values").orElse(null));
+
+ final var testPIIDataAttributes = new HashSet();
+ final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>());
+ final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier()));
+ testPIIDataAttributes.add(piiNameAttribute);
+ testPIIDataAttributes.add(accountNumberAttribute);
+ final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()),
+ new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes));
+ LeiaTestUtils.assertEquals(piiDataListAttribute, LeiaTestUtils.filter(schemaAttributes, "piiDataList").orElse(null));
+
+ final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()),
+ new EnumAttribute("key", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), TestEnum.TWO.name())),
+ new StringAttribute("value", false, new HashSet<>()));
+ LeiaTestUtils.assertEquals(mapAttribute, LeiaTestUtils.filter(schemaAttributes, "map").orElse(null));
+ }
+
+ @Test
+ void testSchemaAttributes_WithRawCollections() {
+ final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestRawCollectionStub.class);
+ Assertions.assertNotNull(schemaAttributes);
+ Assertions.assertEquals(6, schemaAttributes.size());
+
+ final var rawListAttribute = new ArrayAttribute("rawList", false, Set.of(), null);
+ LeiaTestUtils.assertEquals(rawListAttribute, LeiaTestUtils.filter(schemaAttributes, "rawList").orElse(null));
+
+ final var rawLinkedListAttribute = new ArrayAttribute("rawLinkedList", false, Set.of(), null);
+ LeiaTestUtils.assertEquals(rawLinkedListAttribute, LeiaTestUtils.filter(schemaAttributes, "rawLinkedList").orElse(null));
+
+ final var rawSetAttribute = new ArrayAttribute("rawSet", false, Set.of(), null);
+ LeiaTestUtils.assertEquals(rawSetAttribute, LeiaTestUtils.filter(schemaAttributes, "rawSet").orElse(null));
+
+ final var rawHashSetAttribute = new ArrayAttribute("rawHashSet", false, Set.of(), null);
+ LeiaTestUtils.assertEquals(rawHashSetAttribute, LeiaTestUtils.filter(schemaAttributes, "rawHashSet").orElse(null));
+
+ final var rawMapAttribute = new MapAttribute("rawMap", false, Set.of(), null, null);
+ LeiaTestUtils.assertEquals(rawMapAttribute, LeiaTestUtils.filter(schemaAttributes, "rawMap").orElse(null));
+
+ final var rawSortedMapAttribute = new MapAttribute("rawSortedMap", false, Set.of(), null, null);
+ LeiaTestUtils.assertEquals(rawSortedMapAttribute, LeiaTestUtils.filter(schemaAttributes, "rawSortedMap").orElse(null));
+ }
+
+ @Test
+ void testSchemaAttributes_WithObjects() {
+ final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestObjectStub.class);
+ Assertions.assertNotNull(schemaAttributes);
+ Assertions.assertEquals(5, schemaAttributes.size());
+
+ final var objectAttribute = new ObjectAttribute("object", false, Set.of(), null);
+ LeiaTestUtils.assertEquals(objectAttribute, LeiaTestUtils.filter(schemaAttributes, "object").orElse(null));
+
+ final var objectsAttribute = new ArrayAttribute("objects", false, Set.of(),
+ new ObjectAttribute("element", false, Set.of(), null));
+ LeiaTestUtils.assertEquals(objectsAttribute, LeiaTestUtils.filter(schemaAttributes, "objects").orElse(null));
+
+ final var objectListAttribute = new ArrayAttribute("objectList", false, Set.of(),
+ new ObjectAttribute("element", false, Set.of(), null));
+ LeiaTestUtils.assertEquals(objectListAttribute, LeiaTestUtils.filter(schemaAttributes, "objectList").orElse(null));
+
+ final var objectSetAttribute = new ArrayAttribute("objectSet", false, Set.of(),
+ new ObjectAttribute("element", false, Set.of(), null));
+ LeiaTestUtils.assertEquals(objectSetAttribute, LeiaTestUtils.filter(schemaAttributes, "objectSet").orElse(null));
+
+ final var objectMapAttribute = new MapAttribute("objectMap", false, Set.of(),
+ new StringAttribute("key", false, Set.of()),
+ new ObjectAttribute("value", false, Set.of(), null));
+ LeiaTestUtils.assertEquals(objectMapAttribute, LeiaTestUtils.filter(schemaAttributes, "objectMap").orElse(null));
+
+ }
+
+ static class PrimitiveTestClass {
+ @Optional
+ String name;
+ int id;
+ }
+
+ @SchemaDefinition(
+ name = TestRecord.NAME,
+ namespace = TestRecord.NAMESPACE,
+ version = TestRecord.VERSION,
+ description = TestRecord.DESCRIPTION,
+ type = SchemaType.JSON,
+ validation = SchemaValidationType.MATCHING
+ )
+ static class TestRecord {
+ static final String NAME = "TEST_RECORD";
+ static final String NAMESPACE = "test";
+ static final String VERSION = "v1";
+ static final String DESCRIPTION = "Test Record";
+
+ int id;
+ String name;
+ @PII
+ @Encrypted
+ String accountNumber;
+ long ttl;
+ @Optional
+ String accountId;
+ }
+
+ static class TestObject {
+ String name;
+ }
+}
\ No newline at end of file
diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java
new file mode 100644
index 0000000..d30b784
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java
@@ -0,0 +1,23 @@
+package com.grookage.leia.common.stubs;
+
+import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted;
+import com.grookage.leia.models.annotations.attribute.qualifiers.PII;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.jackson.Jacksonized;
+
+@Data
+@Builder
+@Jacksonized
+public class NestedStub {
+ String name;
+ int id;
+ @PII
+ @Encrypted
+ PIIData piiData;
+ @Encrypted
+ RecordStub recordStub;
+ TestEnum enumClass;
+ @PII
+ String phoneNumber;
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java
new file mode 100644
index 0000000..351551d
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java
@@ -0,0 +1,17 @@
+package com.grookage.leia.common.stubs;
+
+import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted;
+import com.grookage.leia.models.annotations.attribute.qualifiers.PII;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.jackson.Jacksonized;
+
+@PII
+@Data
+@Builder
+@Jacksonized
+public class PIIData {
+ String name;
+ @Encrypted
+ String accountNumber;
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java
new file mode 100644
index 0000000..cdd8316
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java
@@ -0,0 +1,12 @@
+package com.grookage.leia.common.stubs;
+
+import com.grookage.leia.models.annotations.attribute.Optional;
+import com.grookage.leia.models.annotations.attribute.qualifiers.PII;
+import lombok.Builder;
+import lombok.extern.jackson.Jacksonized;
+
+@Builder
+@Jacksonized
+public record RecordStub(@PII String name,
+ @Optional int id) {
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java
new file mode 100644
index 0000000..c655da6
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java
@@ -0,0 +1,6 @@
+package com.grookage.leia.common.stubs;
+
+public enum TestEnum {
+ ONE,
+ TWO
+}
diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java
new file mode 100644
index 0000000..d1f6c19
--- /dev/null
+++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java
@@ -0,0 +1,20 @@
+package com.grookage.leia.common.stubs;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.jackson.Jacksonized;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Data
+@Builder
+@Jacksonized
+public class TestObjectStub {
+ Object object;
+ Object[] objects;
+ List
+
+ com.grookage.leia
+ leia-common
+
+
com.grookage.leia
leia-repository
diff --git a/leia-dropwizard-es/pom.xml b/leia-dropwizard-es/pom.xml
index 1f0d83f..9652139 100644
--- a/leia-dropwizard-es/pom.xml
+++ b/leia-dropwizard-es/pom.xml
@@ -22,7 +22,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-dropwizard/pom.xml b/leia-dropwizard/pom.xml
index 5d9fcd8..0bc25d3 100644
--- a/leia-dropwizard/pom.xml
+++ b/leia-dropwizard/pom.xml
@@ -21,7 +21,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java
index 19ef220..2d1f56e 100644
--- a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java
+++ b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java
@@ -18,6 +18,10 @@
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Timed;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.grookage.leia.common.validation.SchemaPayloadValidator;
+import com.grookage.leia.core.exception.LeiaErrorCode;
+import com.grookage.leia.core.exception.LeiaException;
import com.grookage.leia.core.retrieval.SchemaRetriever;
import com.grookage.leia.models.GenericResponse;
import com.grookage.leia.models.request.NamespaceRequest;
@@ -78,4 +82,25 @@ public List getAllSchemaDetails(@Valid final NamespaceRequest nam
return schemaRetriever.getAllSchemaDetails(namespaceRequest.getNamespaces());
}
+ @POST
+ @Timed
+ @ExceptionMetered
+ @Path("/details/validate")
+ public GenericResponse> validateSchema(@Valid SchemaKey schemaKey,
+ @Valid JsonNode jsonNode) {
+ final var schemaDetails = schemaRetriever.getSchemaDetails(schemaKey)
+ .orElseThrow(() -> LeiaException.error(LeiaErrorCode.NO_SCHEMA_FOUND));
+ final var validationErrors = SchemaPayloadValidator.validate(jsonNode, schemaDetails.getValidationType(),
+ schemaDetails.getAttributes());
+ if (validationErrors.isEmpty()) {
+ return GenericResponse.>builder()
+ .success(true)
+ .build();
+ }
+ return GenericResponse.>builder()
+ .data(validationErrors)
+ .build();
+
+ }
+
}
diff --git a/leia-elastic/pom.xml b/leia-elastic/pom.xml
index 9cb71e2..4f2152b 100644
--- a/leia-elastic/pom.xml
+++ b/leia-elastic/pom.xml
@@ -22,7 +22,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-models/pom.xml b/leia-models/pom.xml
index 6f26400..dea8fa5 100644
--- a/leia-models/pom.xml
+++ b/leia-models/pom.xml
@@ -21,7 +21,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java
new file mode 100644
index 0000000..beb9b47
--- /dev/null
+++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java
@@ -0,0 +1,27 @@
+package com.grookage.leia.models.annotations;
+
+import com.grookage.leia.models.schema.SchemaType;
+import com.grookage.leia.models.schema.SchemaValidationType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SchemaDefinition {
+ String name();
+
+ String namespace();
+
+ String version();
+
+ String description() default "";
+
+ SchemaType type() default SchemaType.JSON;
+
+ SchemaValidationType validation() default SchemaValidationType.MATCHING;
+
+ Class>[] transformationTargets() default {};
+}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java
new file mode 100644
index 0000000..c1c92a8
--- /dev/null
+++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java
@@ -0,0 +1,11 @@
+package com.grookage.leia.models.annotations.attribute;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Optional {
+}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java
new file mode 100644
index 0000000..68ef32b
--- /dev/null
+++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java
@@ -0,0 +1,11 @@
+package com.grookage.leia.models.annotations.attribute.qualifiers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Encrypted {
+}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java
new file mode 100644
index 0000000..5336aa5
--- /dev/null
+++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java
@@ -0,0 +1,11 @@
+package com.grookage.leia.models.annotations.attribute.qualifiers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PII {
+}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java
new file mode 100644
index 0000000..cc5e00d
--- /dev/null
+++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java
@@ -0,0 +1,12 @@
+package com.grookage.leia.models.annotations.attribute.qualifiers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShortLived {
+ long ttlSeconds();
+}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java
index 3168147..38d22f1 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -31,9 +33,9 @@ public class ArrayAttribute extends SchemaAttribute {
public ArrayAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo,
+ final Set qualifiers,
SchemaAttribute elementAttribute) {
- super(DataType.ARRAY, name, optional, qualifierInfo);
+ super(DataType.ARRAY, name, optional, qualifiers);
this.elementAttribute = elementAttribute;
}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java
index 89aa779..5c255df 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class BooleanAttribute extends SchemaAttribute {
public BooleanAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.BOOLEAN, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.BOOLEAN, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java
index 5ecc453..7dba446 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java
@@ -23,6 +23,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -31,8 +33,8 @@ public class ByteAttribute extends SchemaAttribute {
public ByteAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.BYTES, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.BYTES, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java
index a454771..55a0ee8 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class DoubleAttribute extends SchemaAttribute {
public DoubleAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.DOUBLE, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.DOUBLE, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java
index 591d22a..10df1ef 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java
@@ -34,9 +34,9 @@ public class EnumAttribute extends SchemaAttribute {
public EnumAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo,
+ final Set qualifiers,
final Set values) {
- super(DataType.ENUM, name, optional, qualifierInfo);
+ super(DataType.ENUM, name, optional, qualifiers);
this.values = values;
}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java
index 85ed563..9c4c7e3 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class FloatAttribute extends SchemaAttribute {
public FloatAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.FLOAT, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.FLOAT, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java
index a3be7d8..7549474 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class IntegerAttribute extends SchemaAttribute {
public IntegerAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.INTEGER, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.INTEGER, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java
index b1efa0c..fa6d2ef 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class LongAttribute extends SchemaAttribute {
public LongAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.LONG, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.LONG, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java
index 31b771f..6f4e8b0 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java
@@ -23,6 +23,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -34,10 +36,10 @@ public class MapAttribute extends SchemaAttribute {
public MapAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo,
+ final Set qualifiers,
SchemaAttribute keyAttribute,
SchemaAttribute valueAttribute) {
- super(DataType.MAP, name, optional, qualifierInfo);
+ super(DataType.MAP, name, optional, qualifiers);
Preconditions.checkArgument(keyAttribute == null || valueAttribute != null);
this.keyAttribute = keyAttribute;
this.valueAttribute = valueAttribute;
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java
index 49892bd..5362346 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java
@@ -33,9 +33,9 @@ public class ObjectAttribute extends SchemaAttribute {
public ObjectAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo,
+ final Set qualifiers,
Set nestedAttributes) {
- super(DataType.OBJECT, name, optional, qualifierInfo);
+ super(DataType.OBJECT, name, optional, qualifiers);
this.nestedAttributes = nestedAttributes;
}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java
index 5121651..e210865 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java
@@ -25,6 +25,7 @@
import lombok.NoArgsConstructor;
import javax.validation.constraints.Pattern;
+import java.util.Set;
@Data
@NoArgsConstructor
@@ -53,7 +54,7 @@ public abstract class SchemaAttribute {
private boolean optional;
- private QualifierInfo qualifierInfo;
+ private Set qualifiers;
public abstract T accept(SchemaAttributeAcceptor attributeAcceptor);
}
diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java
index ee768ac..567b7d5 100644
--- a/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java
+++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java
@@ -22,6 +22,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import java.util.Set;
+
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -30,8 +32,8 @@ public class StringAttribute extends SchemaAttribute {
public StringAttribute(final String name,
final boolean optional,
- final QualifierInfo qualifierInfo) {
- super(DataType.STRING, name, optional, qualifierInfo);
+ final Set qualifiers) {
+ super(DataType.STRING, name, optional, qualifiers);
}
@Override
diff --git a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java
index 7d12b02..b85d714 100644
--- a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java
+++ b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java
@@ -17,7 +17,9 @@
package com.grookage.leia.models.attributes;
import com.grookage.leia.models.ResourceHelper;
+import com.grookage.leia.models.qualifiers.EncryptedQualifier;
import com.grookage.leia.models.qualifiers.QualifierType;
+import com.grookage.leia.models.qualifiers.ShortLivedQualifier;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -40,6 +42,23 @@ void testAttributeStructures() {
Assertions.assertNotNull(attribute);
Assertions.assertEquals("testAttribute", attribute.getName());
Assertions.assertSame(DataType.ARRAY, attribute.getType());
- Assertions.assertTrue(attribute.getQualifierInfo() != null && attribute.getQualifierInfo().getType() == QualifierType.PII);
+ Assertions.assertEquals(1, attribute.getQualifiers().size());
+ Assertions.assertEquals(QualifierType.PII, attribute.getQualifiers().stream().findFirst().get().getType());
+ attribute = ResourceHelper.getResource("attributes/attributeWithMultipleQualifiers.json", SchemaAttribute.class);
+ Assertions.assertNotNull(attribute);
+ Assertions.assertEquals("testAttribute", attribute.getName());
+ Assertions.assertSame(DataType.ARRAY, attribute.getType());
+ Assertions.assertEquals(2, attribute.getQualifiers().size());
+ final var shortLivedQualifier = (ShortLivedQualifier) attribute.getQualifiers().stream()
+ .filter(qualifierInfo -> qualifierInfo.getType().equals(QualifierType.SHORT_LIVED))
+ .findFirst().orElse(null);
+ Assertions.assertNotNull(shortLivedQualifier);
+ Assertions.assertEquals(100, shortLivedQualifier.getTtlSeconds());
+ final var encryptedQualifier = (EncryptedQualifier) attribute.getQualifiers().stream()
+ .filter(qualifierInfo -> qualifierInfo.getType().equals(QualifierType.ENCRYPTED))
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(encryptedQualifier);
+
}
}
diff --git a/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json b/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json
new file mode 100644
index 0000000..4bb9cb7
--- /dev/null
+++ b/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json
@@ -0,0 +1,14 @@
+{
+ "type": "ARRAY",
+ "name": "testAttribute",
+ "optional": true,
+ "qualifiers": [
+ {
+ "type": "ENCRYPTED"
+ },
+ {
+ "type": "SHORT_LIVED",
+ "ttlSeconds": 100
+ }
+ ]
+}
\ No newline at end of file
diff --git a/leia-models/src/test/resources/attributes/attributeWithQualifier.json b/leia-models/src/test/resources/attributes/attributeWithQualifier.json
index 02f792c..e6bbaba 100644
--- a/leia-models/src/test/resources/attributes/attributeWithQualifier.json
+++ b/leia-models/src/test/resources/attributes/attributeWithQualifier.json
@@ -2,7 +2,9 @@
"type": "ARRAY",
"name": "testAttribute",
"optional": true,
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
}
\ No newline at end of file
diff --git a/leia-models/src/test/resources/attributes/enumAttribute.json b/leia-models/src/test/resources/attributes/enumAttribute.json
index 2b9e6ce..14cac5b 100644
--- a/leia-models/src/test/resources/attributes/enumAttribute.json
+++ b/leia-models/src/test/resources/attributes/enumAttribute.json
@@ -5,7 +5,9 @@
"values": [
"TEST_ENUM"
],
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
}
\ No newline at end of file
diff --git a/leia-models/src/test/resources/schema/createSchemaRequest.json b/leia-models/src/test/resources/schema/createSchemaRequest.json
index 9b473a1..64bfa43 100644
--- a/leia-models/src/test/resources/schema/createSchemaRequest.json
+++ b/leia-models/src/test/resources/schema/createSchemaRequest.json
@@ -7,9 +7,11 @@
"type": "ARRAY",
"name": "testAttribute",
"optional": true,
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
}
]
}
\ No newline at end of file
diff --git a/leia-models/src/test/resources/schema/schemaDetails.json b/leia-models/src/test/resources/schema/schemaDetails.json
index 096fb47..af05efd 100644
--- a/leia-models/src/test/resources/schema/schemaDetails.json
+++ b/leia-models/src/test/resources/schema/schemaDetails.json
@@ -14,9 +14,11 @@
"type": "ARRAY",
"name": "testAttribute",
"optional": true,
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
},
{
"type": "ENUM",
@@ -25,9 +27,14 @@
"values": [
"TEST_ENUM"
],
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ },
+ {
+ "type": "ENCRYPTED"
+ }
+ ]
}
],
"transformationTargets": [
diff --git a/leia-models/src/test/resources/schema/updateSchemaRequest.json b/leia-models/src/test/resources/schema/updateSchemaRequest.json
index 570bdd4..d0fd2cf 100644
--- a/leia-models/src/test/resources/schema/updateSchemaRequest.json
+++ b/leia-models/src/test/resources/schema/updateSchemaRequest.json
@@ -9,9 +9,11 @@
"type": "ARRAY",
"name": "testAttribute",
"optional": true,
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
},
{
"type": "ENUM",
@@ -20,9 +22,11 @@
"values": [
"TEST_ENUM"
],
- "qualifierInfo": {
- "type": "PII"
- }
+ "qualifiers": [
+ {
+ "type": "PII"
+ }
+ ]
}
]
}
\ No newline at end of file
diff --git a/leia-parent/pom.xml b/leia-parent/pom.xml
index d9ea9f5..39fb8f7 100644
--- a/leia-parent/pom.xml
+++ b/leia-parent/pom.xml
@@ -21,7 +21,7 @@
com.grookage.leia
leia-bom
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-bom
diff --git a/leia-refresher/pom.xml b/leia-refresher/pom.xml
index 9d95f40..30cb48d 100644
--- a/leia-refresher/pom.xml
+++ b/leia-refresher/pom.xml
@@ -21,7 +21,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-repository/pom.xml b/leia-repository/pom.xml
index 50fc7e6..cc330ef 100644
--- a/leia-repository/pom.xml
+++ b/leia-repository/pom.xml
@@ -22,7 +22,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
diff --git a/leia-schema-validator/pom.xml b/leia-schema-validator/pom.xml
index 2951e32..c6e3ed2 100644
--- a/leia-schema-validator/pom.xml
+++ b/leia-schema-validator/pom.xml
@@ -22,7 +22,7 @@
com.grookage.leia
leia-parent
- 0.0.1-RC6
+ 0.0.1-RC7
../leia-parent
@@ -33,6 +33,7 @@
false
0.9.9
7.0.0
+ 3.11
@@ -40,24 +41,22 @@
com.grookage.leia
leia-models
+
+ com.grookage.leia
+ leia-common
+
reflections
org.reflections
${reflections.version}
-
- leia-models
- test-jar
-
-
- *
- *
-
-
- com.grookage.leia
+ org.apache.commons
+ commons-lang3
+ ${lang3.version}
+
\ No newline at end of file
diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java b/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java
index fe2dce8..5d599b8 100644
--- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java
+++ b/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java
@@ -16,12 +16,12 @@
package com.grookage.leia.validator;
+import com.grookage.leia.common.exception.SchemaValidationException;
+import com.grookage.leia.common.exception.ValidationErrorCode;
+import com.grookage.leia.common.utils.SchemaValidationUtils;
+import com.grookage.leia.models.annotations.SchemaDefinition;
import com.grookage.leia.models.schema.SchemaDetails;
import com.grookage.leia.models.schema.SchemaKey;
-import com.grookage.leia.validator.annotations.SchemaValidatable;
-import com.grookage.leia.validator.exception.SchemaValidationException;
-import com.grookage.leia.validator.exception.ValidationErrorCode;
-import com.grookage.leia.validator.utils.SchemaValidationUtils;
import lombok.Builder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -64,12 +64,12 @@ public void start() {
log.info("Starting the schema validator");
packageRoots.forEach(handlerPackage -> {
final var reflections = new Reflections(handlerPackage);
- final var annotatedClasses = reflections.getTypesAnnotatedWith(SchemaValidatable.class);
+ final var annotatedClasses = reflections.getTypesAnnotatedWith(SchemaDefinition.class);
annotatedClasses.forEach(annotatedClass -> {
- final var annotation = annotatedClass.getAnnotation(SchemaValidatable.class);
+ final var annotation = annotatedClass.getAnnotation(SchemaDefinition.class);
final var schemaKey = SchemaKey.builder()
- .schemaName(annotation.schemaName())
- .version(annotation.versionId())
+ .schemaName(annotation.name())
+ .version(annotation.version())
.namespace(annotation.namespace())
.build();
klassRegistry.putIfAbsent(schemaKey, annotatedClass);
diff --git a/pom.xml b/pom.xml
index dc78d7f..52fc0b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
4.0.0
com.grookage.leia
leia
- 0.0.1-RC6
+ 0.0.1-RC7
pom
Leia
@@ -31,6 +31,7 @@
leia-parent
leia-core
leia-models
+ leia-common
leia-dropwizard
leia-repository
leia-elastic