diff --git a/.gitignore b/.gitignore
index bcebd1c9a..038c2b9dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ target/
.project
.springBeans
.classpath
+
+.externalToolBuilders
+
diff --git a/graphql-jpa-query-schema/pom.xml b/graphql-jpa-query-schema/pom.xml
index 77855d9eb..0eefeb62c 100644
--- a/graphql-jpa-query-schema/pom.xml
+++ b/graphql-jpa-query-schema/pom.xml
@@ -60,6 +60,12 @@
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ test
+
+
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java
index acaddfaae..16d570b27 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaOneToManyDataFetcher.java
@@ -66,10 +66,6 @@ public Object get(DataFetchingEnvironment environment) {
//EntityGraph> entityGraph = buildEntityGraph(new Field("select", new SelectionSet(Arrays.asList(field))));
- // Let's clear session persistent context to avoid getting stale objects cached in the same session
- // between requests with different search criteria. This looks like a Hibernate bug...
- entityManager.clear();
-
return getQuery(environment, field, true)
//.setHint("javax.persistence.fetchgraph", entityGraph) // TODO: fix runtime exception
.getResultList();
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
index 20c07785d..cb58177cd 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
@@ -33,6 +33,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.persistence.Convert;
import javax.persistence.EntityManager;
import javax.persistence.Transient;
import javax.persistence.metamodel.Attribute;
@@ -43,9 +44,6 @@
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter;
@@ -55,7 +53,6 @@
import com.introproventures.graphql.jpa.query.schema.NamingStrategy;
import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.CachedIntrospectionResult.CachedPropertyDescriptor;
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;
-
import graphql.Assert;
import graphql.Scalars;
import graphql.schema.Coercing;
@@ -73,6 +70,8 @@
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.PropertyDataFetcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* JPA specific schema builder implementation of {code #GraphQLSchemaBuilder} interface
@@ -437,6 +436,16 @@ private GraphQLInputType getWhereAttributeType(Attribute,?> attribute) {
.type(getAttributeInputType(attribute))
.build()
);
+ }
+ else if (attribute.getJavaMember().getClass().isAssignableFrom(Field.class)
+ && Field.class.cast(attribute.getJavaMember())
+ .isAnnotationPresent(Convert.class))
+ {
+ builder.field(GraphQLInputObjectField.newInputObjectField()
+ .name(Criteria.LOCATE.name())
+ .description("Locate search criteria")
+ .type(getAttributeInputType(attribute))
+ .build());
}
}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/JpaPredicateBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/JpaPredicateBuilder.java
index 13cf27236..32f0e6930 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/JpaPredicateBuilder.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/JpaPredicateBuilder.java
@@ -414,6 +414,9 @@ else if(Collection.class.isAssignableFrom(type)) {
} else if(type.isEnum()) {
return getEnumPredicate((Path>) field, predicateFilter);
}
+ else if (filter.getCriterias().contains(PredicateFilter.Criteria.LOCATE)) {
+ return cb.gt(cb.locate(from.get(filter.getField()), value.toString()), 0);
+ }
throw new IllegalArgumentException("Unsupported field type " + type + " for field " + predicateFilter.getField());
}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/PredicateFilter.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/PredicateFilter.java
index 7e14bcbbc..8e7080ced 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/PredicateFilter.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/PredicateFilter.java
@@ -103,7 +103,12 @@ public enum Criteria {
/**
* Not Between condition
*/
- NOT_BETWEEN;
+ NOT_BETWEEN,
+
+ /**
+ * JPA's LOCATE predicate for attributes annotated with @Convert
+ */
+ LOCATE;
private static Set names = EnumSet.allOf(Criteria.class)
.stream()
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/GraphQLJpaConverterTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/GraphQLJpaConverterTests.java
new file mode 100644
index 000000000..fa0e2ea2f
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/GraphQLJpaConverterTests.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2017 IntroPro Ventures Inc. and/or its affiliates.
+ *
+ * Licensed 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 com.introproventures.graphql.jpa.query.converter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+import javax.transaction.Transactional;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.introproventures.graphql.jpa.query.converter.model.JsonEntity;
+import com.introproventures.graphql.jpa.query.converter.model.VariableValue;
+import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor;
+import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
+import com.introproventures.graphql.jpa.query.schema.JavaScalars;
+import com.introproventures.graphql.jpa.query.schema.JavaScalars.GraphQLObjectCoercing;
+import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
+import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
+import graphql.schema.GraphQLScalarType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment=WebEnvironment.NONE,
+ properties = "spring.datasource.data=GraphQLJpaConverterTests.sql")
+@TestPropertySource({"classpath:hibernate.properties"})
+public class GraphQLJpaConverterTests {
+
+ @SpringBootApplication
+ static class Application {
+ @Bean
+ public GraphQLExecutor graphQLExecutor(final GraphQLSchemaBuilder graphQLSchemaBuilder) {
+ return new GraphQLJpaExecutor(graphQLSchemaBuilder.build());
+ }
+
+ @Bean
+ public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManager) {
+
+ JavaScalars.register(JsonNode.class, new GraphQLScalarType("Json", "Json type", new GraphQLObjectCoercing()));
+ JavaScalars.register(VariableValue.class, new GraphQLScalarType("VariableValue", "VariableValue Type", new GraphQLObjectCoercing()));
+
+ return new GraphQLJpaSchemaBuilder(entityManager)
+ .name("HashMapSchema")
+ .description("Json Entity test schema");
+ }
+
+ }
+
+ @Autowired
+ private GraphQLExecutor executor;
+
+ @Autowired
+ private EntityManager entityManager;
+
+ @Test
+ public void contextLoads() {
+
+ }
+
+ @Test
+ @Transactional
+ public void queryTester() {
+ // given:
+ Query query = entityManager.createQuery("select json from JsonEntity json where json.attributes LIKE '%key%'");
+
+ // when:
+ List> result = query.getResultList();
+
+ // then:
+ assertThat(result).isNotEmpty();
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ @Transactional
+ public void criteriaTester() {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteria = builder.createQuery(JsonEntity.class);
+ Root json = criteria.from(JsonEntity.class);
+
+ JsonNode value = new ObjectMapper().valueToTree(Collections.singletonMap("attr",
+ new String[] {"1","2","3","4","5"}));
+ criteria.select(json)
+ .where(builder.equal(json.get("attributes"), value));
+
+ // when:
+ List> result = entityManager.createQuery(criteria).getResultList();
+
+ // then:
+ assertThat(result).isNotEmpty();
+ assertThat(result).hasSize(1);
+ }
+
+ @Test // Problem with generating cast() in the where expression
+ @Transactional
+ public void criteriaTesterLike() {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteria = builder.createQuery(JsonEntity.class);
+ Root json = criteria.from(JsonEntity.class);
+
+ criteria.select(json)
+ .where(builder.like(json.get("attributes").as(String.class), "%key%"));
+
+ // when:
+ List> result = entityManager.createQuery(criteria).getResultList();
+
+ // then:
+ assertThat(result).isNotEmpty();
+ assertThat(result).hasSize(1);
+ }
+
+
+ @Test
+ @Transactional
+ public void criteriaTesterLocate() {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteria = builder.createQuery(JsonEntity.class);
+ Root json = criteria.from(JsonEntity.class);
+
+ criteria.select(json)
+ .where(builder.gt(builder.locate(json.get("attributes"),"key"), 0));
+
+ // when:
+ List> result = entityManager.createQuery(criteria).getResultList();
+
+ // then:
+ assertThat(result).isNotEmpty();
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ public void queryJsonEntity() {
+ //given
+ String query = "query {" +
+ " JsonEntities {" +
+ " select {" +
+ " id" +
+ " firstName" +
+ " lastName" +
+ " attributes" +
+ " }" +
+ " }" +
+ "}";
+
+ String expected = "{JsonEntities={select=["
+ + "{id=1, firstName=john, lastName=doe, attributes={\"attr\":{\"key\":[\"1\",\"2\",\"3\",\"4\",\"5\"]}}}, "
+ + "{id=2, firstName=joe, lastName=smith, attributes={\"attr\":[\"1\",\"2\",\"3\",\"4\",\"5\"]}}"
+ + "]}}";
+
+ //when
+ Object result = executor.execute(query).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void queryJsonEntityWhereSearchCriteria() {
+ //given
+ String query = "query {" +
+ " JsonEntities(where: {"
+ + "attributes: {LOCATE: \"key\"}"
+ + "}) {" +
+ " select {" +
+ " id" +
+ " firstName" +
+ " lastName" +
+ " attributes" +
+ " }" +
+ " }" +
+ "}";
+
+ String expected = "{JsonEntities={select=["
+ + "{id=1, firstName=john, lastName=doe, attributes={\"attr\":{\"key\":[\"1\",\"2\",\"3\",\"4\",\"5\"]}}}"
+ + "]}}";
+
+ //when
+ Object result = executor.execute(query).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void queryTaskVariablesWhereSearchCriteria() {
+ //given
+ String query = "query {" +
+ " TaskVariables(where: {"
+ + "value: {LOCATE: \"true\"}"
+ + "}) {" +
+ " select {" +
+ " id" +
+ " name" +
+ " value" +
+ " }" +
+ " }" +
+ "}";
+
+ String expected = "{TaskVariables={select=[{id=2, name=variable2, value=true}]}}";
+
+ //when
+ Object result = executor.execute(query).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void queryTaskVariablesWhereSearchCriteriaVariableBinding() {
+ //given
+ String query = "query($value: VariableValue!) {" +
+ " TaskVariables(where: {"
+ + "value: {LOCATE: $value }"
+ + "}) {" +
+ " select {" +
+ " id" +
+ " name" +
+ " value" +
+ " }" +
+ " }" +
+ "}";
+
+ Map variables = Collections.singletonMap("value", true);
+
+ String expected = "{TaskVariables={select=[{id=2, name=variable2, value=true}]}}";
+
+ //when
+ Object result = executor.execute(query, variables).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+
+ @Test
+ public void queryProcessVariablesWhereSearchCriteriaVariableBindings() {
+ //given
+ String query = "query($value: VariableValue!) {" +
+ " ProcessVariables(where: {"
+ + "value: {LOCATE: $value}"
+ + "}) {" +
+ " select {" +
+ " id" +
+ " name" +
+ " value" +
+ " }" +
+ " }" +
+ "}";
+
+ Map variables = Collections.singletonMap("value", "[\"1\",\"2\",\"3\",\"4\",\"5\"]");
+
+ String expected = "{ProcessVariables={select=[{id=1, name=document, value={key=[1, 2, 3, 4, 5]}}]}}";
+
+ //when
+ Object result = executor.execute(query, variables).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+ @Test
+ public void queryProcessVariablesWhereSearchCriteria() {
+ //given
+ String query = "query {" +
+ " ProcessVariables(where: {"
+ + "value: {LOCATE: \"[\\\"1\\\",\\\"2\\\",\\\"3\\\",\\\"4\\\",\\\"5\\\"]\"}"
+ + "}) {" +
+ " select {" +
+ " id" +
+ " name" +
+ " value" +
+ " }" +
+ " }" +
+ "}";
+
+ String expected = "{ProcessVariables={select=[{id=1, name=document, value={key=[1, 2, 3, 4, 5]}}]}}";
+
+ //when
+ Object result = executor.execute(query).getData();
+
+ // then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/AbstractVariableEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/AbstractVariableEntity.java
new file mode 100644
index 000000000..ad6a89037
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/AbstractVariableEntity.java
@@ -0,0 +1,139 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+@MappedSuperclass
+public abstract class AbstractVariableEntity extends ActivitiEntityMetadata {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String type;
+
+ private String name;
+
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+ private Date createTime;
+
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+ private Date lastUpdatedTime;
+
+ private String executionId;
+
+ @Convert(converter = VariableValueJsonConverter.class)
+ @Column(columnDefinition="text")
+ private VariableValue> value;
+
+ private Boolean markedAsDeleted = false;
+
+ private String processInstanceId;
+
+ public AbstractVariableEntity() {
+ }
+
+ public AbstractVariableEntity(Long id,
+ String type,
+ String name,
+ String processInstanceId,
+ String serviceName,
+ String serviceFullName,
+ String serviceVersion,
+ String appName,
+ String appVersion,
+ Date createTime,
+ Date lastUpdatedTime,
+ String executionId) {
+ super(serviceName,
+ serviceFullName,
+ serviceVersion,
+ appName,
+ appVersion);
+ this.id = id;
+ this.type = type;
+ this.name = name;
+ this.processInstanceId = processInstanceId;
+ this.createTime = createTime;
+ this.lastUpdatedTime = lastUpdatedTime;
+ this.executionId = executionId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ public Date getLastUpdatedTime() {
+ return lastUpdatedTime;
+ }
+
+ public void setLastUpdatedTime(Date lastUpdatedTime) {
+ this.lastUpdatedTime = lastUpdatedTime;
+ }
+
+ public String getExecutionId() {
+ return executionId;
+ }
+
+ public void setExecutionId(String executionId) {
+ this.executionId = executionId;
+ }
+
+ public void setValue(T value) {
+ this.value = new VariableValue<>(value);
+ }
+
+ public T getValue() {
+ return (T) value.getValue();
+ }
+
+ public Boolean getMarkedAsDeleted() {
+ return markedAsDeleted;
+ }
+
+ public void setMarkedAsDeleted(Boolean markedAsDeleted) {
+ this.markedAsDeleted = markedAsDeleted;
+ }
+
+
+ public String getProcessInstanceId() {
+ return processInstanceId;
+ }
+
+
+ public void setProcessInstanceId(String processInstanceId) {
+ this.processInstanceId = processInstanceId;
+ }
+ }
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ActivitiEntityMetadata.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ActivitiEntityMetadata.java
new file mode 100644
index 000000000..9e5c4d26e
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ActivitiEntityMetadata.java
@@ -0,0 +1,78 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+import javax.persistence.MappedSuperclass;
+
+@MappedSuperclass
+public abstract class ActivitiEntityMetadata {
+
+ protected String serviceName;
+ protected String serviceFullName;
+ protected String serviceVersion;
+ protected String appName;
+ protected String appVersion;
+ protected String serviceType;
+
+ public ActivitiEntityMetadata() {
+
+ }
+
+ public ActivitiEntityMetadata(String serviceName,
+ String serviceFullName,
+ String serviceVersion,
+ String appName,
+ String appVersion) {
+ this.serviceName = serviceName;
+ this.serviceFullName = serviceFullName;
+ this.serviceVersion = serviceVersion;
+ this.appName = appName;
+ this.appVersion = appVersion;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getServiceFullName() {
+ return serviceFullName;
+ }
+
+ public void setServiceFullName(String serviceFullName) {
+ this.serviceFullName = serviceFullName;
+ }
+
+ public String getServiceVersion() {
+ return serviceVersion;
+ }
+
+ public void setServiceVersion(String serviceVersion) {
+ this.serviceVersion = serviceVersion;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getAppVersion() {
+ return appVersion;
+ }
+
+ public void setAppVersion(String appVersion) {
+ this.appVersion = appVersion;
+ }
+
+ public String getServiceType() {
+ return serviceType;
+ }
+
+ public void setServiceType(String serviceType) {
+ this.serviceType = serviceType;
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonEntity.java
new file mode 100644
index 000000000..87e161a18
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonEntity.java
@@ -0,0 +1,28 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+
+@Entity(name = "JsonEntity")
+@Table(name = "json_entity")
+@Data
+public class JsonEntity {
+
+ @Id
+ private int id;
+
+ private String firstName;
+
+ private String lastName;
+
+ @Convert(converter = JsonNodeConverter.class)
+ @Column(columnDefinition = "text")
+ private JsonNode attributes;
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonNodeConverter.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonNodeConverter.java
new file mode 100644
index 000000000..2a509db2e
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/JsonNodeConverter.java
@@ -0,0 +1,46 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+import java.io.IOException;
+
+import javax.persistence.AttributeConverter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JsonNodeConverter implements AttributeConverter {
+ private final static Logger logger = LoggerFactory.getLogger(JsonNodeConverter.class);
+
+ private static final ObjectMapper objectMapper = new ObjectMapper()
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ @Override
+ public String convertToDatabaseColumn(JsonNode jsonMap) {
+
+ String jsonString = null;
+ try {
+ jsonString = objectMapper.writeValueAsString(jsonMap);
+ } catch (final JsonProcessingException e) {
+ logger.error("JSON writing error", e);
+ }
+
+ return jsonString;
+ }
+
+ @Override
+ public JsonNode convertToEntityAttribute(String jsonString) {
+
+ JsonNode jsonMap = null;
+ try {
+ jsonMap = objectMapper.readValue(jsonString, JsonNode.class);
+
+ } catch (final IOException e) {
+ logger.error("JSON reading error", e);
+ }
+
+ return jsonMap;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ProcessVariableEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ProcessVariableEntity.java
new file mode 100644
index 000000000..74822a46c
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/ProcessVariableEntity.java
@@ -0,0 +1,52 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity(name="ProcessVariable")
+@Table(name = "PROCESS_VARIABLE")
+public class ProcessVariableEntity extends AbstractVariableEntity {
+
+ public ProcessVariableEntity() {
+ }
+
+ public ProcessVariableEntity(Long id,
+ String type,
+ String name,
+ String processInstanceId,
+ String serviceName,
+ String serviceFullName,
+ String serviceVersion,
+ String appName,
+ String appVersion,
+ Date createTime,
+ Date lastUpdatedTime,
+ String executionId) {
+ super(id,
+ type,
+ name,
+ processInstanceId,
+ serviceName,
+ serviceFullName,
+ serviceVersion,
+ appName,
+ appVersion,
+ createTime,
+ lastUpdatedTime,
+ executionId);
+
+ }
+
+ public String getTaskId() {
+ return null;
+ }
+
+ public boolean isTaskVariable() {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/TaskVariableEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/TaskVariableEntity.java
new file mode 100644
index 000000000..9ea3365b0
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/TaskVariableEntity.java
@@ -0,0 +1,58 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity(name="TaskVariable")
+@Table(name = "TASK_VARIABLE")
+public class TaskVariableEntity extends AbstractVariableEntity {
+
+ private String taskId;
+
+ public TaskVariableEntity() {
+ }
+
+ public TaskVariableEntity(Long id,
+ String type,
+ String name,
+ String processInstanceId,
+ String serviceName,
+ String serviceFullName,
+ String serviceVersion,
+ String appName,
+ String appVersion,
+ String taskId,
+ Date createTime,
+ Date lastUpdatedTime,
+ String executionId) {
+ super(id,
+ type,
+ name,
+ processInstanceId,
+ serviceName,
+ serviceFullName,
+ serviceVersion,
+ appName,
+ appVersion,
+ createTime,
+ lastUpdatedTime,
+ executionId);
+
+ this.taskId = taskId;
+ }
+
+ public String getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(String taskId) {
+ this.taskId = taskId;
+ }
+
+ public boolean isTaskVariable() {
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValue.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValue.java
new file mode 100644
index 000000000..23b4e14b9
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValue.java
@@ -0,0 +1,58 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+
+public class VariableValue {
+
+ private T value;
+
+ public VariableValue() {
+ }
+
+ public VariableValue(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+
+ /**
+ * Encountered Java type [class org.activiti.cloud.services.query.model.VariableValue] for which we could not locate a JavaTypeDescriptor
+ * and which does not appear to implement equals and/or hashCode. This can lead to significant performance problems when performing
+ * equality/dirty checking involving this Java type.
+ *
+ * Consider registering a custom JavaTypeDescriptor or at least implementing equals/hashCode.
+ *
+ */
+ @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;
+ VariableValue> other = (VariableValue>) obj;
+ if (value == null) {
+ if (other.value != null)
+ return false;
+ } else if (!value.equals(other.value))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "VariableValue [value=" + value + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValueJsonConverter.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValueJsonConverter.java
new file mode 100644
index 000000000..75211e219
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/converter/model/VariableValueJsonConverter.java
@@ -0,0 +1,39 @@
+package com.introproventures.graphql.jpa.query.converter.model;
+
+
+import java.io.IOException;
+
+import javax.persistence.AttributeConverter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.hibernate.QueryException;
+
+public class VariableValueJsonConverter implements AttributeConverter, String> {
+
+ private static ObjectMapper objectMapper = new ObjectMapper()
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+ public VariableValueJsonConverter() {
+ }
+
+ @Override
+ public String convertToDatabaseColumn(VariableValue> variableValue) {
+ try {
+ return objectMapper.writeValueAsString(variableValue);
+ } catch (JsonProcessingException e) {
+ throw new QueryException("Unable to serialize variable.", e);
+ }
+ }
+
+ @Override
+ public VariableValue> convertToEntityAttribute(String dbData) {
+ try {
+ return objectMapper.readValue(dbData, VariableValue.class);
+ } catch (IOException e) {
+ throw new QueryException("Unable to deserialize variable.", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/test/resources/GraphQLJpaConverterTests.sql b/graphql-jpa-query-schema/src/test/resources/GraphQLJpaConverterTests.sql
new file mode 100644
index 000000000..28a15be2d
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/resources/GraphQLJpaConverterTests.sql
@@ -0,0 +1,15 @@
+-- Json entity
+insert into json_entity (id, first_name, last_name, attributes) values
+ (1, 'john', 'doe', '{"attr":{"key":["1","2","3","4","5"]}}'),
+ (2, 'joe', 'smith', '{"attr":["1","2","3","4","5"]}');
+
+insert into PROCESS_VARIABLE (create_time, execution_id, last_updated_time, name, process_instance_id, type, value) values
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'document', 1, 'json', '{"value":{"key":["1","2","3","4","5"]}}');
+
+insert into TASK_VARIABLE (create_time, execution_id, last_updated_time, name, process_instance_id, task_id, type, value) values
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable1', 0, '1', 'string', '{"value":"data"}'),
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable2', 0, '1', 'boolean', '{"value":true}'),
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable3', 0, '2', 'string', '{"value":null}'),
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable4', 0, '2', 'json', '{"value":{"key":"data"}}'),
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable5', 1, '4', 'double', '{"value":1.0}'),
+ (CURRENT_TIMESTAMP, 'execution_id', CURRENT_TIMESTAMP, 'variable6', 1, '4', 'json', '{"value":[1,2,3,4,5]}');
\ No newline at end of file