Skip to content

Implementation calculations fields (recreate branch) #85

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

Merged
merged 2 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.introproventures.graphql.jpa.query.schema.impl;


import javax.persistence.Transient;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class CashGraphQLCalculatedFields {
protected static Map<Class, Map<String, Optional<Transient>>> cashCalcFields = new ConcurrentHashMap<>();

public static void clearCashCalcFields() {
cashCalcFields.values().stream().forEach(v -> v.clear());
cashCalcFields.clear();
}

public static boolean isCalcField(Class cls, String field) {
if (cashCalcFields.containsKey(cls)) {
if (cashCalcFields.get(cls).containsKey(field)) {
return cashCalcFields.get(cls).get(field).isPresent();
}
}

Optional<Transient> cf = getTransient(cls, field);
addCashCalcFields(cls, field, cf);

return cf.isPresent();
}

public static void addCashCalcFields(Class cls, String field, Optional<Transient> an) {
if (!cashCalcFields.containsKey(cls)) {
Map<String, Optional<Transient>> tpMap = new ConcurrentHashMap<>();
cashCalcFields.put(cls, tpMap);
}

cashCalcFields.get(cls).put(field, an);
}

public static Optional<Transient> getTransient(Class cls, String field) {
Optional<Transient> calcField = Arrays.stream(cls.getDeclaredFields())
.filter(f -> f.getName().equals(field) && f.isAnnotationPresent(Transient.class))
.map(f -> f.getAnnotation(Transient.class))
.findFirst();

if (!calcField.isPresent()) {
calcField = getGraphQLCalcMethod(cls, field, "get");
}

if (!calcField.isPresent()) {
calcField = getGraphQLCalcMethod(cls, field, "is");
}

return calcField;
}

public static Optional<Transient> getGraphQLCalcMethod(Class cls, String field, String prefix) {
String methodName = prefix + field.substring(0,1).toUpperCase() + field.substring(1);

return Arrays.stream(cls.getDeclaredMethods())
.filter(m -> m.getName().equals(methodName) && m.isAnnotationPresent(Transient.class))
.map(m -> m.getAnnotation(Transient.class))
.findFirst()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,13 @@
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.persistence.Transient;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
Expand Down Expand Up @@ -469,13 +466,77 @@ private GraphQLObjectType getObjectType(EntityType<?> entityType) {
.map(this::getObjectField)
.collect(Collectors.toList())
)
.fields(getObjectCalcFields(entityType.getJavaType()))
.fields(getObjectCalcMethods(entityType.getJavaType()))
.build();

entityCache.putIfAbsent(entityType, objectType);

return objectType;
}

private List<GraphQLFieldDefinition> getObjectCalcFields(Class cls) {
return
Arrays.stream(cls.getDeclaredFields())
.filter(
f ->
f instanceof Member &&
f.isAnnotationPresent(Transient.class) &&
isNotIgnored((Member) f) &&
isNotIgnored(f.getType())
)
.map(f -> getObjectCalcField(f))
.collect(Collectors.toList());
}

private List<GraphQLFieldDefinition> getObjectCalcMethods(Class cls) {
return
Arrays.stream(cls.getDeclaredMethods())
.filter(
m ->
m instanceof Member &&
m.isAnnotationPresent(Transient.class) &&
isNotIgnored((Member) m) &&
isNotIgnored(m.getReturnType())
)
.map(m -> getObjectCalcMethtod(m))
.collect(Collectors.toList());
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectCalcField(Field field) {
GraphQLType type = getGraphQLTypeFromJavaType(field.getType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(field.getName());

return GraphQLFieldDefinition.newFieldDefinition()
.name(field.getName())
.description(getSchemaDescription((AnnotatedElement) field))
.type((GraphQLOutputType) type)
.dataFetcher(dataFetcher)
.build();
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectCalcMethtod(Method method) {
String nm = method.getName();
if (nm.startsWith("is")) {
nm = Introspector.decapitalize(nm.substring(2));
}
if (nm.startsWith("get")) {
nm = Introspector.decapitalize(nm.substring(3));
}

GraphQLType type = getGraphQLTypeFromJavaType(method.getReturnType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(nm);

return GraphQLFieldDefinition.newFieldDefinition()
.name(nm)
.description(getSchemaDescription((AnnotatedElement) method))
.type((GraphQLOutputType) type)
.dataFetcher(dataFetcher)
.build();
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectField(Attribute attribute) {
GraphQLOutputType type = getAttributeOutputType(attribute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,7 @@
import static graphql.introspection.Introspection.TypeMetaFieldDef;
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -146,7 +137,7 @@ protected final List<Argument> getFieldArguments(Field field, CriteriaQuery<?> q
Field selectedField = (Field) selection;

// "__typename" is part of the graphql introspection spec and has to be ignored by jpa
if(!TYPENAME.equals(selectedField.getName())) {
if(!TYPENAME.equals(selectedField.getName()) && !CashGraphQLCalculatedFields.isCalcField(from.getJavaType(), selectedField.getName())) {

Path<?> fieldPath = from.get(selectedField.getName());

Expand Down Expand Up @@ -728,7 +719,7 @@ && isManagedType(entityType.getAttribute(it.getName()))
Subgraph<?> sg = entityGraph.addSubgraph(it.getName());
buildSubgraph(it, sg);
} else {
if(!TYPENAME.equals(it.getName()))
if(!TYPENAME.equals(it.getName()) && !CashGraphQLCalculatedFields.isCalcField(entityType.getJavaType(), it.getName()))
entityGraph.addAttributeNodes(it.getName());
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.introproventures.graphql.jpa.query.schema;

import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
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.context.annotation.Bean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
@TestPropertySource({"classpath:hibernate.properties"})
public class CalcEntityTests {
@SpringBootApplication
static class Application {
@Bean
public GraphQLExecutor graphQLExecutor(final GraphQLSchemaBuilder graphQLSchemaBuilder) {
return new GraphQLJpaExecutor(graphQLSchemaBuilder.build());
}

@Bean
public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManager) {

return new GraphQLJpaSchemaBuilder(entityManager)
.name("GraphQLCalcFields")
.description("CalcFields JPA test schema");
}

}

@Autowired
private GraphQLExecutor executor;

@Test
public void contextLoads() {
Assert.isAssignable(GraphQLExecutor.class, executor.getClass());
}

@Test
public void getAllRecords() {
//given
String query = "query GraphQLCalcFields { CalcEntities { select {id title fieldMem fieldFun logic customLogic } } }";

String expected = "{CalcEntities={select=[{id=1, title=title 1, fieldMem=member, fieldFun=title 1 function, logic=true, customLogic=false}, {id=2, title=title 2, fieldMem=member, fieldFun=title 2 function, logic=true, customLogic=false}]}}";

//when
Object result = executor.execute(query).getData();

//then
assertThat(result.toString()).isEqualTo(expected);
}

@Test
public void testIgnoreFields() {
String query = "query GraphQLCalcFields { CalcEntities { select {id title fieldMem fieldFun logic customLogic hideField hideFieldFunction } } }";

String expected = "[ValidationError{validationErrorType=FieldUndefined, queryPath=[CalcEntities, select, hideField], message=Validation error of type FieldUndefined: Field 'hideField' in type 'CalcEntity' is undefined @ 'CalcEntities/select/hideField', locations=[SourceLocation{line=1, column=95}], description='Field 'hideField' in type 'CalcEntity' is undefined'}, ValidationError{validationErrorType=FieldUndefined, queryPath=[CalcEntities, select, hideFieldFunction], message=Validation error of type FieldUndefined: Field 'hideFieldFunction' in type 'CalcEntity' is undefined @ 'CalcEntities/select/hideFieldFunction', locations=[SourceLocation{line=1, column=105}], description='Field 'hideFieldFunction' in type 'CalcEntity' is undefined'}]";

//when
Object result = executor.execute(query).getErrors();

//then
assertThat(result.toString()).isEqualTo(expected);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.introproventures.graphql.jpa.query.schema.model.calc;

import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;

@Data
@Entity
public class CalcEntity {
@Id
Long id;

String title;

String info;

@Transient
boolean logic = true;

@Transient
@GraphQLDescription("i desc member")
String fieldMem = "member";

@Transient
@GraphQLIgnore
String hideField = "hideField";

@Transient
@GraphQLDescription("i desc function")
public String getFieldFun() {
return title + " function";
}

@Transient
public boolean isCustomLogic() {
return false;
}

public String getHideFieldFunction() {
return "getHideFieldFunction";
}
}
6 changes: 5 additions & 1 deletion graphql-jpa-query-schema/src/test/resources/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,8 @@ insert into Boat (id, country, identification) values
(1, 'EN', '12345'),
(2, 'EN', '23456'),
(1, 'FR', '34567');


-- Calculate entity
insert into calc_entity (id, title, info) values
(1, 'title 1', 'inf 1'),
(2, 'title 2', 'inf 2');