diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/json/JsonUtils.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/json/JsonUtils.java index c660fb31ce..2db647fb85 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/json/JsonUtils.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/json/JsonUtils.java @@ -21,6 +21,7 @@ import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.kie.kogito.jackson.utils.MergeUtils; import org.kie.kogito.jackson.utils.ObjectMapperFactory; +import org.kie.kogito.persistence.api.query.AttributeFilter; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -58,4 +59,10 @@ private static ObjectNode createObjectNode(String variableName, Object variableV } return result; } + + public static AttributeFilter jsonFilter(AttributeFilter filter) { + filter.setJson(true); + return filter; + } + } diff --git a/data-index/data-index-graphql/pom.xml b/data-index/data-index-graphql/pom.xml index 09c51edd0d..ef774baa25 100644 --- a/data-index/data-index-graphql/pom.xml +++ b/data-index/data-index-graphql/pom.xml @@ -45,6 +45,10 @@ io.quarkus quarkus-vertx-graphql + + com.graphql-java + graphql-java-extended-scalars + io.quarkus quarkus-reactive-routes diff --git a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java index 20a333d5b0..1d9c208392 100644 --- a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java +++ b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java @@ -57,6 +57,7 @@ import static org.kie.kogito.persistence.api.query.QueryFilterFactory.not; import static org.kie.kogito.persistence.api.query.QueryFilterFactory.notNull; import static org.kie.kogito.persistence.api.query.QueryFilterFactory.or; +import static org.kie.kogito.index.json.JsonUtils.jsonFilter; public class GraphQLQueryMapper implements Function { @@ -110,15 +111,43 @@ public GraphQLQueryParser apply(GraphQLInputObjectType type) { case "KogitoMetadataArgument": parser.mapAttribute(field.getName(), mapSubEntityArgument(field.getName(), GraphQLQueryParserRegistry.get().getParser("KogitoMetadataArgument"))); break; + case "JSON": + parser.mapAttribute(field.getName(), mapJsonArgument(field.getName())); + break; default: - parser.mapAttribute(field.getName(), mapSubEntityArgument(field.getName(), new GraphQLQueryMapper().apply((GraphQLInputObjectType) field.getType()))); + if (field.getType() instanceof GraphQLInputObjectType) { + parser.mapAttribute(field.getName(), mapSubEntityArgument(field.getName(), new GraphQLQueryMapper().apply((GraphQLInputObjectType) field.getType()))); + } } } }); - return parser; } + private Function>> mapJsonArgument(String attribute) { + return argument -> ((Map) argument).entrySet().stream().map(e -> mapJsonArgument(attribute, e.getKey(), e.getValue())); + } + + private AttributeFilter mapJsonArgument(String attribute, String key, Object value) { + StringBuilder sb = new StringBuilder(attribute); + FilterCondition condition = FilterCondition.fromLabel(key); + while (condition == null && value instanceof Map) { + sb.append('.').append(key); + Map.Entry entry = ((Map) value).entrySet().iterator().next(); + key = entry.getKey(); + value = entry.getValue(); + condition = FilterCondition.fromLabel(key); + } + if (condition != null) { + switch (condition) { + case EQUAL: + default: + return jsonFilter(equalTo(sb.toString(), value)); + } + } + return null; + } + private boolean isListOfType(GraphQLInputType source, String type) { if (isList(source)) { return ((GraphQLNamedType) unwrapNonNull(unwrapOne(source))).getName().equals(type); diff --git a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls index 0317ec61c5..f30e90c7ed 100644 --- a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls +++ b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls @@ -177,6 +177,7 @@ input ProcessInstanceArgument { id: IdArgument processId: StringArgument processName: StringArgument + variables: JSON parentProcessInstanceId: IdArgument rootProcessInstanceId: IdArgument rootProcessId: StringArgument diff --git a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java index af18b2821c..2acaf539ab 100644 --- a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java +++ b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java @@ -76,6 +76,7 @@ public GraphQLSchema createSchema() { typeDefinitionRegistry.merge(loadSchemaDefinitionFile("domain.schema.graphqls")); RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() + .scalar(ExtendedScalars.Json) .type("Query", builder -> { builder.dataFetcher("ProcessDefinitions", this::getProcessDefinitionsValues); builder.dataFetcher("ProcessInstances", this::getProcessInstancesValues); diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/JPAQuery.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/JPAQuery.java index 3e1fbcade6..4ab1f7d6db 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/JPAQuery.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/JPAQuery.java @@ -43,13 +43,13 @@ public class JPAQuery implements Query { - private PanacheRepositoryBase repository; + protected final PanacheRepositoryBase repository; private Integer limit; private Integer offset; private List> filters; private List sortBy; - private Class entityClass; - private Function mapper; + protected final Class entityClass; + protected final Function mapper; public JPAQuery(PanacheRepositoryBase repository, Function mapper, Class entityClass) { this.repository = repository; @@ -113,57 +113,60 @@ protected List getPredicates(CriteriaBuilder builder, Root root) { return filters.stream().map(filterPredicateFunction(root, builder)).collect(toList()); } - private Function, Predicate> filterPredicateFunction(Root root, CriteriaBuilder builder) { - return filter -> { - switch (filter.getCondition()) { - case CONTAINS: - return builder.isMember(filter.getValue(), getAttributePath(root, filter.getAttribute())); - case CONTAINS_ALL: - List predicatesAll = (List) ((List) filter.getValue()).stream() - .map(o -> builder.isMember(o, getAttributePath(root, filter.getAttribute()))).collect(toList()); - return builder.and(predicatesAll.toArray(new Predicate[] {})); - case CONTAINS_ANY: - List predicatesAny = (List) ((List) filter.getValue()).stream() - .map(o -> builder.isMember(o, getAttributePath(root, filter.getAttribute()))).collect(toList()); - return builder.or(predicatesAny.toArray(new Predicate[] {})); - case IN: - return getAttributePath(root, filter.getAttribute()).in((Collection) filter.getValue()); - case LIKE: - return builder.like(getAttributePath(root, filter.getAttribute()), - filter.getValue().toString().replaceAll("\\*", "%")); - case EQUAL: - return builder.equal(getAttributePath(root, filter.getAttribute()), filter.getValue()); - case IS_NULL: - Path pathNull = getAttributePath(root, filter.getAttribute()); - return isPluralAttribute(filter.getAttribute()) ? builder.isEmpty(pathNull) : builder.isNull(pathNull); - case NOT_NULL: - Path pathNotNull = getAttributePath(root, filter.getAttribute()); - return isPluralAttribute(filter.getAttribute()) ? builder.isNotEmpty(pathNotNull) : builder.isNotNull(pathNotNull); - case BETWEEN: - List value = (List) filter.getValue(); - return builder - .between(getAttributePath(root, filter.getAttribute()), (Comparable) value.get(0), - (Comparable) value.get(1)); - case GT: - return builder.greaterThan(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); - case GTE: - return builder.greaterThanOrEqualTo(getAttributePath(root, filter.getAttribute()), - (Comparable) filter.getValue()); - case LT: - return builder.lessThan(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); - case LTE: - return builder - .lessThanOrEqualTo(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); - case OR: - return builder.or(getRecursivePredicate(filter, root, builder).toArray(new Predicate[] {})); - case AND: - return builder.and(getRecursivePredicate(filter, root, builder).toArray(new Predicate[] {})); - case NOT: - return builder.not(filterPredicateFunction(root, builder).apply((AttributeFilter) filter.getValue())); - default: - return null; - } - }; + protected Function, Predicate> filterPredicateFunction(Root root, CriteriaBuilder builder) { + return filter -> buildPredicateFunction(filter, root, builder); + } + + protected final Predicate buildPredicateFunction(AttributeFilter filter, Root root, CriteriaBuilder builder) { + switch (filter.getCondition()) { + case CONTAINS: + return builder.isMember(filter.getValue(), getAttributePath(root, filter.getAttribute())); + case CONTAINS_ALL: + List predicatesAll = (List) ((List) filter.getValue()).stream() + .map(o -> builder.isMember(o, getAttributePath(root, filter.getAttribute()))).collect(toList()); + return builder.and(predicatesAll.toArray(new Predicate[] {})); + case CONTAINS_ANY: + List predicatesAny = (List) ((List) filter.getValue()).stream() + .map(o -> builder.isMember(o, getAttributePath(root, filter.getAttribute()))).collect(toList()); + return builder.or(predicatesAny.toArray(new Predicate[] {})); + case IN: + return getAttributePath(root, filter.getAttribute()).in((Collection) filter.getValue()); + case LIKE: + return builder.like(getAttributePath(root, filter.getAttribute()), + filter.getValue().toString().replaceAll("\\*", "%")); + case EQUAL: + return builder.equal(getAttributePath(root, filter.getAttribute()), filter.getValue()); + case IS_NULL: + Path pathNull = getAttributePath(root, filter.getAttribute()); + return isPluralAttribute(filter.getAttribute()) ? builder.isEmpty(pathNull) : builder.isNull(pathNull); + case NOT_NULL: + Path pathNotNull = getAttributePath(root, filter.getAttribute()); + return isPluralAttribute(filter.getAttribute()) ? builder.isNotEmpty(pathNotNull) : builder.isNotNull(pathNotNull); + case BETWEEN: + List value = (List) filter.getValue(); + return builder + .between(getAttributePath(root, filter.getAttribute()), (Comparable) value.get(0), + (Comparable) value.get(1)); + case GT: + return builder.greaterThan(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); + case GTE: + return builder.greaterThanOrEqualTo(getAttributePath(root, filter.getAttribute()), + (Comparable) filter.getValue()); + case LT: + return builder.lessThan(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); + case LTE: + return builder + .lessThanOrEqualTo(getAttributePath(root, filter.getAttribute()), (Comparable) filter.getValue()); + case OR: + return builder.or(getRecursivePredicate(filter, root, builder).toArray(new Predicate[] {})); + case AND: + return builder.and(getRecursivePredicate(filter, root, builder).toArray(new Predicate[] {})); + case NOT: + return builder.not(filterPredicateFunction(root, builder).apply((AttributeFilter) filter.getValue())); + default: + return null; + } + } private Path getAttributePath(Root root, String attribute) { diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java index 68e5d24844..17a3c82904 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java @@ -48,6 +48,8 @@ import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.storage.ProcessInstanceStorage; +import io.quarkus.arc.DefaultBean; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -57,6 +59,7 @@ import static org.kie.kogito.index.DateTimeUtils.toZonedDateTime; @ApplicationScoped +@DefaultBean public class ProcessInstanceEntityStorage extends AbstractJPAStorageFetcher implements ProcessInstanceStorage { protected ProcessInstanceEntityStorage() { diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonJPAQuery.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonJPAQuery.java new file mode 100644 index 0000000000..8a0d84294f --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonJPAQuery.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.kie.kogito.index.postgresql; + +import java.util.function.Function; + +import org.kie.kogito.index.jpa.model.AbstractEntity; +import org.kie.kogito.index.jpa.storage.JPAQuery; +import org.kie.kogito.persistence.api.query.AttributeFilter; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +public class PostgresqlJsonJPAQuery extends JPAQuery { + + public PostgresqlJsonJPAQuery(PanacheRepositoryBase repository, Function mapper, Class entityClass) { + super(repository, mapper, entityClass); + } + + protected Function, Predicate> filterPredicateFunction(Root root, CriteriaBuilder builder) { + return filter -> filter.isJson() ? PostgresqlJsonNavigator.buildPredicate(filter, root, builder) : buildPredicateFunction(filter, root, builder); + } +} diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonNavigator.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonNavigator.java new file mode 100644 index 0000000000..b40235f612 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonNavigator.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.kie.kogito.index.postgresql; + +import org.kie.kogito.persistence.api.query.AttributeFilter; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +public class PostgresqlJsonNavigator { + + private PostgresqlJsonNavigator() { + } + + public static Predicate buildPredicate(AttributeFilter filter, Root root, + CriteriaBuilder builder) { + switch (filter.getCondition()) { + case EQUAL: + boolean isString = filter.getValue() instanceof String; + return builder.equal(buildPathExpression(builder, root, filter.getAttribute(), isString), buildObjectExpression(builder, filter.getValue(), isString)); + } + throw new UnsupportedOperationException(); + } + + private static Expression buildObjectExpression(CriteriaBuilder builder, Object value, boolean isString) { + return isString ? builder.literal(value) : builder.function("to_jsonb", Object.class, builder.literal(value)); + } + + private static Expression buildPathExpression(CriteriaBuilder builder, Root root, String attributeName, boolean isStr) { + String[] attributes = attributeName.split("\\."); + Expression[] arguments = new Expression[attributes.length]; + arguments[0] = root.get(attributes[0]); + for (int i = 1; i < attributes.length; i++) { + arguments[i] = builder.literal(attributes[i]); + } + return isStr ? builder.function("jsonb_extract_path_text", String.class, arguments) : builder.function("jsonb_extract_path", Object.class, arguments); + } + +} diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessInstanceEntityStorage.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessInstanceEntityStorage.java new file mode 100644 index 0000000000..db6a001568 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessInstanceEntityStorage.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.kie.kogito.index.postgresql; + +import org.kie.kogito.index.jpa.mapper.ProcessInstanceEntityMapper; +import org.kie.kogito.index.jpa.model.ProcessInstanceEntityRepository; +import org.kie.kogito.index.jpa.storage.ProcessInstanceEntityStorage; +import org.kie.kogito.index.model.ProcessInstance; +import org.kie.kogito.persistence.api.query.Query; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class PostgresqlProcessInstanceEntityStorage extends ProcessInstanceEntityStorage { + + @Inject + public PostgresqlProcessInstanceEntityStorage(ProcessInstanceEntityRepository repository, ProcessInstanceEntityMapper mapper) { + super(repository, mapper); + } + + @Override + public Query query() { + return new PostgresqlJsonJPAQuery<>(repository, mapToModel, entityClass); + } +} diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java index 254bca48f2..aada6ea0ed 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java @@ -18,13 +18,38 @@ */ package org.kie.kogito.index.postgresql.query; +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.kie.kogito.event.process.ProcessInstanceVariableDataEvent; import org.kie.kogito.index.jpa.query.AbstractProcessInstanceEntityQueryIT; +import org.kie.kogito.index.storage.ProcessInstanceStorage; +import org.kie.kogito.index.test.TestUtils; import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.kogito.index.json.JsonUtils.jsonFilter; +import static org.kie.kogito.index.test.QueryTestUtils.assertWithId; +import static org.kie.kogito.persistence.api.query.QueryFilterFactory.equalTo; + @QuarkusTest @QuarkusTestResource(PostgreSqlQuarkusTestResource.class) class ProcessInstanceEntityQueryIT extends AbstractProcessInstanceEntityQueryIT { + + @Test + void testProcessInstanceVariables() { + String processId = "travels"; + String processInstanceId = UUID.randomUUID().toString(); + ProcessInstanceStorage storage = getStorage(); + ProcessInstanceVariableDataEvent variableEvent = TestUtils.createProcessInstanceVariableEvent(processInstanceId, processId, "John", "Smith"); + storage.indexVariable(variableEvent); + queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(equalTo("variables.traveller.firstName", "John"))), null, null, null, + processInstanceId); + queryAndAssert((instances, ids) -> assertThat(instances).isEmpty(), storage, singletonList(jsonFilter(equalTo("variables.traveller.firstName", "Smith"))), null, null, null, + processInstanceId); + } } diff --git a/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/AttributeFilter.java b/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/AttributeFilter.java index a7427ba8eb..2577d78f3c 100644 --- a/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/AttributeFilter.java +++ b/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/AttributeFilter.java @@ -26,6 +26,8 @@ public class AttributeFilter { private T value; + private transient boolean jsonFilter; + protected AttributeFilter(String attribute, FilterCondition condition, T value) { this.attribute = attribute; this.condition = condition; @@ -56,6 +58,14 @@ public void setValue(T value) { this.value = value; } + public void setJson(boolean jsonFilter) { + this.jsonFilter = jsonFilter; + } + + public boolean isJson() { + return jsonFilter; + } + @Override public boolean equals(Object o) { if (this == o) {