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

Added Array, Set and operator 'IN' #110

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion thunx-bom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies {
api project(':thunx-encoding-json')
api project(':thunx-predicates-querydsl')

api 'com.contentgrid.opa-java-client:opa-async-java-client:0.4.0'
api 'com.contentgrid.opa-java-client:opa-async-java-client:0.4.1-SNAPSHOT'
}
}

ystrict marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.contentgrid.thunx.encoding.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class CollectionValueDeserializer extends StdDeserializer {

private final ObjectMapper objectMapper;

public CollectionValueDeserializer() {
this(null);
}

public CollectionValueDeserializer(Class<?> vc) {
super(vc);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonExpressionDto.class, this);
mapper.registerModule(module);
this.objectMapper = mapper;
}


@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
String type = node.get("type").asText();

if ("array".equals(type)) {
List<JsonScalarDto<?>> list = new ArrayList<>();
Iterator<JsonNode> values = node.get("value").elements();

while (values.hasNext()) {
JsonNode value = values.next();
list.add(deserializeInnerValue(value));
}
return new JsonCollectionValueDto("array", list);
} else if ("set".equals(type)) {
Set<JsonScalarDto<?>> set = new HashSet<>();

Iterator<JsonNode> values = node.get("value").elements();
while (values.hasNext()) {
JsonNode value = values.next();
set.add(deserializeInnerValue(value));
}

return new JsonCollectionValueDto("set", set);
}

throw new IllegalArgumentException("Unsupported type: " + type);
}

private JsonScalarDto<?> deserializeInnerValue(JsonNode data) {
return (JsonScalarDto<?>) objectMapper.convertValue(data, JsonExpressionDto.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.contentgrid.thunx.encoding.json;

import com.contentgrid.thunx.predicates.model.CollectionValue;
import com.contentgrid.thunx.predicates.model.ContextFreeThunkExpressionVisitor;
import com.contentgrid.thunx.predicates.model.FunctionExpression;
import com.contentgrid.thunx.predicates.model.Scalar;
import com.contentgrid.thunx.predicates.model.SymbolicReference;
import com.contentgrid.thunx.predicates.model.ThunkExpression;
import com.contentgrid.thunx.predicates.model.ContextFreeThunkExpressionVisitor;
import com.contentgrid.thunx.predicates.model.Variable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.contentgrid.thunx.predicates.model.FunctionExpression;
import com.contentgrid.thunx.predicates.model.Variable;

import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.stream.Collectors;

public class ExpressionJsonConverter {
Expand All @@ -17,7 +22,11 @@ public class ExpressionJsonConverter {
private final JsonEncoderVisitor visitor = new JsonEncoderVisitor();

public ExpressionJsonConverter() {
this(new ObjectMapper());
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonExpressionDto.class, new CollectionValueDeserializer(JsonCollectionValueDto.class));
mapper.registerModule(module);
this.objectMapper = mapper;
}

public ExpressionJsonConverter(ObjectMapper objectMapper) {
Expand All @@ -44,15 +53,7 @@ private static class JsonEncoderVisitor extends ContextFreeThunkExpressionVisito

@Override
public JsonExpressionDto visit(Scalar<?> scalar) {
var resultType = scalar.getResultType();

var typeName = JsonScalarDto.SCALAR_TYPES.get(resultType);
if (typeName == null) {
String exMessage = String.format("Scalar of type <%s> is not supported", resultType.getName());
throw new IllegalArgumentException(exMessage);
}

return JsonScalarDto.of(typeName, scalar.getValue());
return processScalar(scalar);
}

@Override
Expand All @@ -75,8 +76,66 @@ public JsonExpressionDto visit(Variable variable) {
return new JsonVariableDto(variable.getName());
}

@Override
protected JsonExpressionDto visit(CollectionValue collectionValue) {
return processCollectionDto(collectionValue);
}
}

private static class JsonScalarEncoderVisitor extends ContextFreeThunkExpressionVisitor<JsonScalarDto<?>> {

@Override
protected JsonScalarDto<?> visit(Scalar<?> scalar) {
return processScalar(scalar);
}

@Override
protected JsonScalarDto<?> visit(FunctionExpression<?> functionExpression) {
throw new UnsupportedOperationException("Only Scalars are supported in Collection");
}

@Override
protected JsonScalarDto<?> visit(SymbolicReference symbolicReference) {
throw new UnsupportedOperationException("Only Scalars are supported in Collection");
}

@Override
protected JsonScalarDto<?> visit(Variable variable) {
throw new UnsupportedOperationException("Only Scalars are supported in Collection");
}

@Override
protected JsonScalarDto<?> visit(CollectionValue collectionValue) {
return processCollectionDto(collectionValue);
}
}

private static JsonScalarDto<?> processScalar(Scalar<?> scalar) {
var resultType = scalar.getResultType();

var typeName = JsonScalarDto.SCALAR_TYPES.get(resultType);
if (typeName == null) {
String exMessage = String.format("Scalar of type <%s> is not supported", resultType.getName());
throw new IllegalArgumentException(exMessage);
}

return JsonScalarDto.of(typeName, scalar.getValue());
}

private static JsonScalarDto<?> processCollectionDto(CollectionValue collectionValue) {
Collection<JsonExpressionDto> result;
if (JsonCollectionValueDto.getTypeByClass(collectionValue.getResultType()).equals("set")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need to do typechecks here, this indicates that we need two separate visit methods: one for the set type and one for the array type.

This also means that we need two separate classes: CollectionValue needs to be split up in ArrayCollectionValue and SetCollectionValue. You can keep CollectionValue as an interface so it can be used in places where an array or a set would be sufficient.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, I agree with you, but can we merge this PR and create issue, for this? I will pick it up and fix in next PR, cause this one became to big and too time consuming. This change requires rewriting the whole PR:(

result = collectionValue.getValue().stream()
.map(scalar -> scalar.accept(new JsonScalarEncoderVisitor(), null))
.collect(Collectors.toSet());
} else if (JsonCollectionValueDto.getTypeByClass(collectionValue.getResultType()).equals("array")) {
result = collectionValue.getValue().stream()
.map(scalar -> scalar.accept(new JsonScalarEncoderVisitor(), null)).collect(Collectors.toList());
} else {
throw new UnsupportedOperationException("Type is not supported: " + collectionValue.getResultType());
}

return JsonCollectionValueDto.of(JsonCollectionValueDto.getTypeByClass(collectionValue.getResultType()), result);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.contentgrid.thunx.encoding.json;

import com.contentgrid.thunx.predicates.model.Scalar;
import com.contentgrid.thunx.predicates.model.ThunkExpression;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.util.stream.Collectors;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.NonNull;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@JsonDeserialize(using = CollectionValueDeserializer.class)
public class JsonCollectionValueDto extends JsonScalarDto<Collection<JsonScalarDto<?>>> {

private static final Map<Class<?>, String> COLLECTION_TYPES = Map.of(
Set.class, "set",
List.class, "array");

public JsonCollectionValueDto(String type, Collection<JsonScalarDto<?>> value) {
super(type, value);
}

@Override
public ThunkExpression<?> toExpression() throws InvalidExpressionDataException {
switch (this.getType()) {
case "set":
Set<JsonScalarDto<?>> setValues = (Set) super.getValue();
Set<? extends ThunkExpression<?>> setExpressions = setValues.stream()
.map(JsonScalarDto::toExpression).collect(Collectors.toSet());
return Scalar.of((Collection<Scalar<?>>) setExpressions);
case "array":
List<JsonScalarDto<?>> listValues = (List) super.getValue();
List<? extends ThunkExpression<?>> listExpressions = listValues.stream()
.map(JsonScalarDto::toExpression).collect(Collectors.toList());
return Scalar.of((Collection<Scalar<?>>) listExpressions);
default:
String message = String.format("Collection of type: '%s' is not supported", this.getType());
throw new UnsupportedOperationException(message);
}
}

public static JsonCollectionValueDto of(String type, @NonNull Collection<JsonScalarDto<?>> value) {
return new JsonCollectionValueDto(type, value);
}

public static String getTypeByClass(Class type) {
return COLLECTION_TYPES.entrySet()
.stream()
.filter(e -> e.getKey().isAssignableFrom(type))
.map(Map.Entry::getValue)
.findFirst().orElseThrow(() -> new UnsupportedOperationException("Such type is not supported for collection: " + type));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
@Type(value = JsonScalarDto.class, name = "bool"),
@Type(value = JsonScalarDto.class, name = "null"),
@Type(value = JsonVariableDto.class, name = "var"),
@Type(value = JsonSymbolicReferenceDto.class, name = "ref")
@Type(value = JsonSymbolicReferenceDto.class, name = "ref"),
@Type(value = JsonCollectionValueDto.class, name = "array"),
@Type(value = JsonCollectionValueDto.class, name = "set")
})
public interface JsonExpressionDto {

Expand Down
Loading