Commit 367bf5b7 authored by Matija Obreza's avatar Matija Obreza

Deserialize JSON filters to internal objects

parent 9c2de3eb
package org.genesys2.server.model.filters;
public class NoSuchFilterException extends Exception {
public NoSuchFilterException(String message) {
super(message);
}
}
package org.genesys2.server.model.filters;
public class NoSuchFilterValueException extends Exception {
private Object filterValue;
public NoSuchFilterValueException(Object filterValue) {
this.filterValue = filterValue;
}
public Object getFilterValue() {
return filterValue;
}
@Override
public String toString() {
return super.toString() + " Invalid value: " + filterValue;
}
}
package org.genesys2.server.model.filters;
public class UnsupportedFilterOperation extends Exception {
public UnsupportedFilterOperation(String message) {
super(message);
}
}
......@@ -19,8 +19,10 @@ package org.genesys2.server.service.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
......@@ -35,6 +37,9 @@ import org.genesys2.server.model.filters.GenesysFilter.DataType;
import org.genesys2.server.model.filters.GenesysFilter.FilterType;
import org.genesys2.server.model.filters.I18nListFilter;
import org.genesys2.server.model.filters.MethodFilter;
import org.genesys2.server.model.filters.NoSuchFilterException;
import org.genesys2.server.model.filters.NoSuchFilterValueException;
import org.genesys2.server.model.filters.UnsupportedFilterOperation;
import org.genesys2.server.model.filters.ValueName;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.model.genesys.TraitCode;
......@@ -43,7 +48,18 @@ import org.genesys2.server.service.TraitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
/**
* Converts filter requests (usually JSON) to internal data structures
......@@ -155,24 +171,562 @@ public class FilterHandler {
return filter;
}
public List<AppliedFilter> fromJSON(String jsonFilter) throws IOException {
Map<String, List<String>> filters = objectMapper.readValue(jsonFilter, Map.class);
List<AppliedFilter> appliedFilters = new ArrayList<AppliedFilter>();
public List<AppliedFilter> fromJSON(String jsonFilter) throws IOException, NoSuchFilterException, UnsupportedFilterOperation, NoSuchFilterValueException {
@SuppressWarnings("unchecked")
Map<String, List<Object>> filters = objectMapper.readValue(jsonFilter, Map.class);
AppliedFilters appliedFilters = new AppliedFilters();
for (String key : filters.keySet()) {
List<String> filterValues = filters.get(key);
List<Object> filterValues = filters.get(key);
if (filterValues == null || filterValues.isEmpty())
continue;
LOG.info("key=" + key);
AppliedFilter appliedFilter = new AppliedFilter();
appliedFilter.setFilter(getFilterByName(key));
for (Object filterValue : filterValues) {
appliedFilter.addFilterValue(toFilterValue(filterValue));
}
}
return appliedFilters;
}
private static class AppliedFilter {
private FilterValue toFilterValue(Object filterValue) throws UnsupportedFilterOperation, NoSuchFilterValueException {
if (filterValue == null)
return null;
if (filterValue instanceof Map) {
return toFilterOperation((Map<?, ?>) filterValue);
} else if (filterValue instanceof Number || filterValue instanceof String) {
return new LiteralValueFilter(filterValue);
} else {
throw new NoSuchFilterValueException(filterValue);
}
}
private FilterValue toFilterOperation(Map<?, ?> filterValue) throws UnsupportedFilterOperation {
@SuppressWarnings("unchecked")
List<Number> range = (List<Number>) filterValue.get("range");
if (range != null) {
return new ValueRangeFilter(range.get(0), range.get(1));
}
Number max = (Number) filterValue.get("max");
if (max != null) {
return new MaxValueFilter(max);
}
Number min = (Number) filterValue.get("min");
if (min != null) {
return new MinValueFilter(min);
}
String like = (String) filterValue.get("like");
if (like != null) {
return new StartsWithFilter(like);
}
throw new UnsupportedFilterOperation(filterValue.toString());
}
private GenesysFilter getFilterByName(String key) throws NoSuchFilterException {
for (GenesysFilter filter : this.availableFilters) {
if (filter.getKey().equals(key)) {
return filter;
}
}
throw new NoSuchFilterException(key);
}
public static interface FilterValue {
}
@JsonSerialize(using = AppliedFilters.Serializer.class)
@JsonDeserialize(using = AppliedFilters.Deserializer.class)
public static class AppliedFilters extends ArrayList<AppliedFilter> {
public static class Serializer extends JsonSerializer<AppliedFilters> {
@Override
public void serialize(AppliedFilters filters, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
for (AppliedFilter filter : filters) {
jgen.writeArrayFieldStart(filter.getFilterName());
for (FilterValue fv : filter.getValues())
jgen.writeObject(fv);
if (filter.withNull)
jgen.writeNull();
jgen.writeEndArray();
}
jgen.writeEndObject();
}
}
public static class Deserializer extends StdDeserializer<AppliedFilters> {
public Deserializer() {
super(AppliedFilters.class);
}
protected Deserializer(Class<AppliedFilters> vc) {
super(vc);
}
@Override
public AppliedFilters deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw new IOException("invalid start marker");
}
final AppliedFilters appliedFilters = new AppliedFilters();
jp.nextToken();
while (jp.getCurrentToken() == JsonToken.FIELD_NAME) {
AppliedFilter af = new AppliedFilter();
appliedFilters.add(af);
af.setFilterName(jp.getCurrentName());
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
jp.nextToken();
do {
if (jp.getCurrentToken() == JsonToken.VALUE_STRING) {
af.addFilterValue(new LiteralValueFilter(jp.getText()));
} else if (jp.getCurrentToken() == JsonToken.VALUE_TRUE || jp.getCurrentToken() == JsonToken.VALUE_FALSE) {
af.addFilterValue(new LiteralValueFilter(jp.getBooleanValue()));
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
af.addFilterValue(new LiteralValueFilter(jp.getDoubleValue()));
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
af.addFilterValue(new LiteralValueFilter(jp.getLongValue()));
} else if (jp.getCurrentToken() == JsonToken.VALUE_NULL) {
af.addFilterValue(null);
} else if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
jp.nextToken();
String op = jp.getCurrentName();
if ("like".equals(op)) {
af.addFilterValue(new StartsWithFilter(jp.nextTextValue()));
} else if ("min".equals(op)) {
Number number1 = null;
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
number1 = jp.getDoubleValue();
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
number1 = jp.getLongValue();
}
if (number1 == null)
throw new JsonParseException("MinValueFilter expects a single numeric value", jp.getCurrentLocation());
af.addFilterValue(new MinValueFilter(number1));
} else if ("max".equals(op)) {
Number number1 = null;
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
number1 = jp.getDoubleValue();
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
number1 = jp.getLongValue();
}
if (number1 == null)
throw new JsonParseException("MaxValueFilter expects a single numeric value", jp.getCurrentLocation());
af.addFilterValue(new MaxValueFilter(number1));
} else if ("range".equals(op)) {
if (jp.nextToken() == JsonToken.START_ARRAY) {
Number number1 = null, number2 = null;
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
number1 = jp.getDoubleValue();
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
number1 = jp.getLongValue();
}
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
number2 = jp.getDoubleValue();
} else if (jp.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
number2 = jp.getLongValue();
}
if (number1 == null || number2 == null || jp.nextToken() != JsonToken.END_ARRAY)
throw new JsonParseException("ValueRangeFilter expects two numeric values in an array", jp.getCurrentLocation());
af.addFilterValue(new ValueRangeFilter(number1, number2));
} else {
throw new JsonParseException("ValueRangeFilter expects an array of values", jp.getCurrentLocation());
}
}
if (jp.nextToken() != JsonToken.END_OBJECT)
throw new JsonParseException("Expecting }, got " + jp.getCurrentToken(), jp.getCurrentLocation());
} else {
throw new JsonParseException("I don't know where I am.", jp.getCurrentLocation());
}
jp.nextToken();
} while (jp.getCurrentToken() != JsonToken.END_ARRAY);
} else {
System.err.println(jp.getCurrentToken());
throw new JsonParseException("Filter values must be provided in an array", jp.getCurrentLocation());
}
if (jp.nextToken() == JsonToken.END_OBJECT)
break;
// System.err.println(jp.getCurrentToken());
}
return appliedFilters;
}
}
}
public static class AppliedFilter {
private GenesysFilter filter;
private Set<FilterValue> values = new HashSet<FilterValue>();
private boolean withNull = false;
private String filterName;
public void setFilter(GenesysFilter filter) {
this.filter = filter;
}
public String getFilterName() {
return this.filterName;
}
public AppliedFilter setFilterName(String filterName) {
this.filterName = filterName;
return this;
}
public AppliedFilter addFilterValue(FilterValue filterValue) {
if (filterValue == null) {
this.withNull = true;
} else {
values.add(filterValue);
}
return this;
}
public GenesysFilter getFilter() {
return filter;
}
public boolean getWithNull() {
return this.withNull;
}
public Set<FilterValue> getValues() {
return values;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((filterName == null) ? 0 : filterName.hashCode());
result = prime * result + ((values == null) ? 0 : values.hashCode());
result = prime * result + (withNull ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AppliedFilter other = (AppliedFilter) obj;
if (filterName == null) {
if (other.filterName != null)
return false;
} else if (!filterName.equals(other.filterName))
return false;
if (values == null) {
if (other.values != null)
return false;
} else if (!values.equals(other.values))
return false;
if (withNull != other.withNull)
return false;
return true;
}
}
@JsonSerialize(using = LiteralValueFilter.Serializer.class)
public static class LiteralValueFilter implements FilterValue {
public static class Serializer extends JsonSerializer<LiteralValueFilter> {
@Override
public void serialize(LiteralValueFilter value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeObject(value.getValue());
}
}
private Object value;
public LiteralValueFilter(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LiteralValueFilter other = (LiteralValueFilter) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
@JsonSerialize(using = ValueRangeFilter.Serializer.class)
public static class ValueRangeFilter implements FilterValue {
public static class Serializer extends JsonSerializer<ValueRangeFilter> {
@Override
public void serialize(ValueRangeFilter value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeArrayFieldStart("range");
jgen.writeObject(value.getFrom());
jgen.writeObject(value.getTo());
jgen.writeEndArray();
jgen.writeEndObject();
}
}
private Number from;
private Number to;
public ValueRangeFilter(Number number1, Number number2) {
if (number1 == null || number2 == null)
throw new NullPointerException("ValueRangeFilter requires non-null values for range");
if (number1 instanceof Integer)
number1 = number1.longValue();
if (number1 instanceof Float)
number1 = number1.doubleValue();
if (number2 instanceof Integer)
number2 = number2.longValue();
if (number2 instanceof Float)
number2 = number2.doubleValue();
this.from = number1.doubleValue() < number2.doubleValue() ? number1 : number2;
this.to = number1.doubleValue() < number2.doubleValue() ? number2 : number1;
}
public Number getFrom() {
return from;
}
public Number getTo() {
return to;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((from == null) ? 0 : from.hashCode());
result = prime * result + ((to == null) ? 0 : to.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ValueRangeFilter other = (ValueRangeFilter) obj;
if (from == null) {
if (other.from != null)
return false;
} else if (!from.equals(other.from))
return false;
if (to == null) {
if (other.to != null)
return false;
} else if (!to.equals(other.to))
return false;
return true;
}
}
@JsonSerialize(using = MaxValueFilter.Serializer.class)
public static class MaxValueFilter implements FilterValue {
public static class Serializer extends JsonSerializer<MaxValueFilter> {
@Override
public void serialize(MaxValueFilter value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField("max", value.getTo());
jgen.writeEndObject();
}
}
private Number to;
public MaxValueFilter(Number max) {
if (max instanceof Integer)
max = max.longValue();
else if (max instanceof Float)
max = max.doubleValue();
this.to = max;
}
public Number getTo() {
return to;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((to == null) ? 0 : to.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MaxValueFilter other = (MaxValueFilter) obj;
if (to == null) {
if (other.to != null)
return false;
} else if (!to.equals(other.to))
return false;
return true;
}
}
@JsonSerialize(using = MinValueFilter.Serializer.class)
public static class MinValueFilter implements FilterValue {
public static class Serializer extends JsonSerializer<MinValueFilter> {
@Override
public void serialize(MinValueFilter value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField("min", value.getFrom());
jgen.writeEndObject();
}
}
private Number from;
public MinValueFilter(Number min) {
if (min instanceof Integer)
this.from = min.longValue();
else if (min instanceof Float)
this.from = min.doubleValue();
else
this.from = min;
}
public Number getFrom() {
return from;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((from == null) ? 0 : from.hashCode());
return result;
}