Skip to content

Commit

Permalink
review changes and changelog additions
Browse files Browse the repository at this point in the history
  • Loading branch information
Gunda Abhishek committed Dec 2, 2024
1 parent 5ba56cb commit 279ad6c
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 443 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
All notable changes to this project will be documented in this file.
## [0.0.1-RC7]

- Introduced utils classes for building SchemaAttributes from Class and validating a json payload
against the SchemaDetails of a specified schema key
- Introduced a `leia-common` module to host all the common utils classes
- Removed the `SchemaValidatable` annotation and moved it to a generic `SchemaDefinition`
- Few Bug fixes in SchemaValidationUtils
- 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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,36 @@

package com.grookage.leia.common.builder;

import com.grookage.leia.common.utils.SchemaAttributeUtils;
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 {
Expand All @@ -37,8 +60,199 @@ public Optional<CreateSchemaRequest> buildSchemaRequest(final Class<?> klass) {
.description(schemaDefinition.description())
.schemaType(schemaDefinition.type())
.validationType(schemaDefinition.validation())
.attributes(SchemaAttributeUtils.getSchemaAttributes(klass))
.attributes(getSchemaAttributes(klass))
.build()
);
}

public Set<SchemaAttribute> 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<QualifierInfo> 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<String>, Map<String, Integer>)
if (type instanceof ParameterizedType parameterizedType) {
return schemaAttribute(parameterizedType, name, qualifiers, optional);
}

// Handle GenericArrayType (e.g., T[], List<T[]>)
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<QualifierInfo> qualifiers,
final boolean optional) {
final var rawType = (Class<?>) parameterizedType.getRawType();
// Handle List<T> or Set<T>
if (ClassUtils.isAssignable(rawType, Collection.class)) {
return handleCollection(parameterizedType, name, qualifiers, optional);
}

// Handle Map<T,R>
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<QualifierInfo> 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<QualifierInfo> 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<QualifierInfo> 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<QualifierInfo> 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<QualifierInfo> 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<String> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,20 @@

package com.grookage.leia.common.utils;

import com.grookage.leia.common.exception.SchemaValidationException;
import com.grookage.leia.common.exception.ValidationErrorCode;
import lombok.experimental.UtilityClass;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@UtilityClass
public class Utils {
public class FieldUtils {
public List<Field> getAllFields(final Class<?> type) {
List<Field> fields = new ArrayList<>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}

public 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;
}

public Set<String> getEnumValues(final Class<?> klass) {
return Arrays.stream(klass.getEnumConstants())
.map(enumConstant -> ((Enum<?>) enumConstant).name())
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public Optional<QualifierInfo> filter(final Set<QualifierInfo> qualifiers,
.findFirst();
}

public Set<QualifierInfo> getQualifierInfo(final Type type) {
public Set<QualifierInfo> getQualifiers(final Type type) {
if (type instanceof Class<?> klass) {
return getQualifierInfo(klass);
return getQualifiers(klass);
}
return new HashSet<>();
}

public Set<QualifierInfo> getQualifierInfo(final Field field) {
public Set<QualifierInfo> getQualifiers(final Field field) {
Set<QualifierInfo> qualifiers = new HashSet<>();
if (field.isAnnotationPresent(Encrypted.class)) {
qualifiers.add(new EncryptedQualifier());
Expand All @@ -67,7 +67,7 @@ public Set<QualifierInfo> getQualifierInfo(final Field field) {
return qualifiers;
}

public Set<QualifierInfo> getQualifierInfo(final Class<?> klass) {
public Set<QualifierInfo> getQualifiers(final Class<?> klass) {
Set<QualifierInfo> qualifiers = new HashSet<>();
if (klass.isAnnotationPresent(Encrypted.class)) {
qualifiers.add(new EncryptedQualifier());
Expand Down
Loading

0 comments on commit 279ad6c

Please sign in to comment.