Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Util classes for building and validating schema + leia-common #11

Merged
merged 13 commits into from
Dec 3, 2024
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

All notable changes to this project will be documented in this file.
## [0.0.1-RC7]

- LeiaClient - SchemaUtil to add support for building SchemaAttributes from the Schema class
- LeiaCore - Added API support to validate schema payload against the SchemaDetails of a specified key

## [0.0.1-RC6]

- LeiaClient - Introduced AuthHeaderSupplier in LeiaClientSupplier to support authentication on leia-server
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Leia is a governance and metadata framework aimed at meeting compliance requirem
<dependency>
<groupId>com.grookage.leia</groupId>
<artifactId>leia-bom</artifactId>
<versio>0.0.1-RC6</version>
<versio>0.0.1-RC7</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion leia-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.grookage.leia</groupId>
<artifactId>leia</artifactId>
<version>0.0.1-RC6</version>
<version>0.0.1-RC7</version>
</parent>

<artifactId>leia-bom</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion leia-client-dropwizard/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.grookage.leia</groupId>
<artifactId>leia-parent</artifactId>
<version>0.0.1-RC6</version>
<version>0.0.1-RC7</version>
<relativePath>../leia-parent</relativePath>
</parent>

Expand Down
9 changes: 8 additions & 1 deletion leia-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
<parent>
<groupId>com.grookage.leia</groupId>
<artifactId>leia-parent</artifactId>
<version>0.0.1-RC6</version>
<version>0.0.1-RC7</version>
<relativePath>../leia-parent</relativePath>
</parent>

<artifactId>leia-client</artifactId>

<properties>
<lang3.version>3.11</lang3.version>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

Expand All @@ -53,6 +54,12 @@
<groupId>org.projectlombok</groupId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${lang3.version}</version>
</dependency>

<dependency>
<artifactId>junit-jupiter</artifactId>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package com.grookage.leia.client.utils;

import com.grookage.leia.models.annotations.Optional;
import com.grookage.leia.models.annotations.qualifiers.Encrypted;
import com.grookage.leia.models.annotations.qualifiers.PII;
import com.grookage.leia.models.annotations.qualifiers.ShortLived;
import com.grookage.leia.models.annotations.qualifiers.Standard;
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.EncryptedQualifier;
import com.grookage.leia.models.qualifiers.PIIQualifier;
import com.grookage.leia.models.qualifiers.QualifierInfo;
import com.grookage.leia.models.qualifiers.ShortLivedQualifier;
import com.grookage.leia.models.qualifiers.StandardQualifier;
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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@UtilityClass
public class SchemaUtil {
koushikr marked this conversation as resolved.
Show resolved Hide resolved
public Set<SchemaAttribute> buildSchemaAttributes(final Class<?> klass) {
return getAllFields(klass)
koushikr marked this conversation as resolved.
Show resolved Hide resolved
.stream().map(SchemaUtil::schemaAttribute)
.collect(Collectors.toSet());
}

private SchemaAttribute schemaAttribute(final Field field) {
return schemaAttribute(
field.getGenericType(),
field.getName(),
getQualifierInfo(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) {
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
koushikr marked this conversation as resolved.
Show resolved Hide resolved
// 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(ParameterizedType parameterizedType,
String name,
Set<QualifierInfo> qualifiers,
boolean optional) {
final var keyType = parameterizedType.getActualTypeArguments()[0];
final var valueType = parameterizedType.getActualTypeArguments()[1];
return new MapAttribute(
name,
optional,
qualifiers,
schemaAttribute(keyType, "key", getQualifierInfo(keyType), isOptional(keyType)),
schemaAttribute(valueType, "value", getQualifierInfo(valueType), isOptional(valueType))
);
}

private SchemaAttribute handleCollection(ParameterizedType parameterizedType,
String name,
Set<QualifierInfo> qualifiers, boolean optional) {
final var elementType = parameterizedType.getActualTypeArguments()[0];
return new ArrayAttribute(
name,
optional,
qualifiers,
schemaAttribute(elementType, "element", getQualifierInfo(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", getQualifierInfo(componentType), isOptional(componentType))
);
}


private SchemaAttribute schemaAttribute(final Class<?> klass,
final String name,
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));
}

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", getQualifierInfo(componentType), isOptional(componentType))
);
}

// Handling custom defined POJO's
final var schemaAttributes = buildSchemaAttributes(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 List<Field> getAllFields(Class<?> type) {
koushikr marked this conversation as resolved.
Show resolved Hide resolved
List<Field> fields = new ArrayList<>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}

private Set<String> getEnumValues(Class<?> klass) {
return Arrays.stream(klass.getEnumConstants())
.map(enumConstant -> ((Enum<?>) enumConstant).name())
.collect(Collectors.toSet());
}

private Set<QualifierInfo> getQualifierInfo(Field field) {
Set<QualifierInfo> qualifierInfos = new HashSet<>();
koushikr marked this conversation as resolved.
Show resolved Hide resolved
if (field.isAnnotationPresent(Encrypted.class)) {
qualifierInfos.add(new EncryptedQualifier());
}
if (field.isAnnotationPresent(Standard.class)) {
koushikr marked this conversation as resolved.
Show resolved Hide resolved
qualifierInfos.add(new StandardQualifier());
}
if (field.isAnnotationPresent(PII.class)) {
qualifierInfos.add(new PIIQualifier());
}
if (field.isAnnotationPresent(ShortLived.class)) {
final var shortLived = field.getAnnotation(ShortLived.class);
qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds()));
}
return qualifierInfos;
}

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

private Set<QualifierInfo> getQualifierInfo(Class<?> klass) {
Set<QualifierInfo> qualifierInfos = new HashSet<>();
koushikr marked this conversation as resolved.
Show resolved Hide resolved
if (klass.isAnnotationPresent(Encrypted.class)) {
qualifierInfos.add(new EncryptedQualifier());
}
if (klass.isAnnotationPresent(Standard.class)) {
koushikr marked this conversation as resolved.
Show resolved Hide resolved
qualifierInfos.add(new StandardQualifier());
}
if (klass.isAnnotationPresent(PII.class)) {
qualifierInfos.add(new PIIQualifier());
}
if (klass.isAnnotationPresent(ShortLived.class)) {
final var shortLived = klass.getAnnotation(ShortLived.class);
qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds()));
}
return qualifierInfos;
}

private boolean isOptional(Type type) {
if (type instanceof Class<?> klass) {
return isOptional(klass);
}
return false;
}

private boolean isOptional(Class<?> klass) {
return klass.isAnnotationPresent(Optional.class);
}

private boolean isOptional(Field field) {
return field.isAnnotationPresent(Optional.class);
}
}
Loading
Loading