Skip to content

Commit 09807ac

Browse files
igdianovIntroPro Ventures
authored andcommitted
Support @MappedSuperclass and @Embedded jpa annotations (#35)
1 parent 0df8a98 commit 09807ac

File tree

8 files changed

+172
-37
lines changed

8 files changed

+172
-37
lines changed

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import javax.persistence.EntityManager;
3232
import javax.persistence.metamodel.Attribute;
33+
import javax.persistence.metamodel.EmbeddableType;
3334
import javax.persistence.metamodel.EntityType;
3435
import javax.persistence.metamodel.PluralAttribute;
3536
import javax.persistence.metamodel.SingularAttribute;
@@ -90,6 +91,7 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder {
9091

9192
private Map<Class<?>, GraphQLType> classCache = new HashMap<>();
9293
private Map<EntityType<?>, GraphQLObjectType> entityCache = new HashMap<>();
94+
private Map<EmbeddableType<?>, GraphQLObjectType> embeddableCache = new HashMap<>();
9395

9496
private static final Logger log = LoggerFactory.getLogger(GraphQLJpaSchemaBuilder.class);
9597

@@ -127,14 +129,6 @@ private GraphQLObjectType getQueryType() {
127129
.collect(Collectors.toList())
128130
);
129131

130-
// queryType.fields(
131-
// entityManager.getMetamodel()
132-
// .getEntities().stream()
133-
// .filter(this::isNotIgnored)
134-
// .map(this::getQueryFieldDefinition)
135-
// .collect(Collectors.toList())
136-
// );
137-
138132
queryType.fields(
139133
entityManager.getMetamodel()
140134
.getEntities().stream()
@@ -146,21 +140,6 @@ private GraphQLObjectType getQueryType() {
146140
return queryType.build();
147141
}
148142

149-
private GraphQLFieldDefinition getQueryFieldDefinition(EntityType<?> entityType) {
150-
return GraphQLFieldDefinition.newFieldDefinition()
151-
.name(namingStrategy.pluralize(entityType.getName()))
152-
.description(getSchemaDescription( entityType.getJavaType()))
153-
.type(new GraphQLList(getObjectType(entityType)))
154-
.dataFetcher(new QraphQLJpaBaseDataFetcher(entityManager, entityType))
155-
.argument(entityType.getAttributes().stream()
156-
.filter(this::isValidInput)
157-
.filter(this::isNotIgnored)
158-
.map(this::getArgument)
159-
.collect(Collectors.toList())
160-
)
161-
.build();
162-
}
163-
164143
private GraphQLFieldDefinition getQueryFieldByIdDefinition(EntityType<?> entityType) {
165144
return GraphQLFieldDefinition.newFieldDefinition()
166145
.name(entityType.getName())
@@ -179,13 +158,6 @@ private GraphQLFieldDefinition getQueryFieldByIdDefinition(EntityType<?> entityT
179158

180159
private GraphQLFieldDefinition getQueryFieldSelectDefinition(EntityType<?> entityType) {
181160

182-
GraphQLArgument distinctArgument = GraphQLArgument.newArgument()
183-
.name(SELECT_DISTINCT_PARAM_NAME)
184-
.description("JPA Query DISTINCT specification")
185-
.type(Scalars.GraphQLBoolean)
186-
.defaultValue(true)
187-
.build();
188-
189161
GraphQLObjectType pageType = GraphQLObjectType.newObject()
190162
.name(namingStrategy.pluralize(entityType.getName()))
191163
.description("Query response wrapper object for " + entityType.getName() + ". When page is requested, this object will be returned with query metadata.")
@@ -205,7 +177,6 @@ private GraphQLFieldDefinition getQueryFieldSelectDefinition(EntityType<?> entit
205177
.name(GraphQLJpaSchemaBuilder.QUERY_SELECT_PARAM_NAME)
206178
.description("The queried records container")
207179
.type(new GraphQLList(getObjectType(entityType)))
208-
//.argument(distinctArgument)
209180
.build()
210181
)
211182
.build();
@@ -286,12 +257,13 @@ private GraphQLInputObjectField getWhereInputField(Attribute<?,?> attribute) {
286257
private Map<String, GraphQLInputType> whereAttributesMap = new HashMap<>();
287258

288259
private GraphQLInputType getWhereAttributeType(Attribute<?,?> attribute) {
289-
String type = namingStrategy.singularize(attribute.getName())+((EntityType<?>)attribute.getDeclaringType()).getName()+"Criteria";
260+
//String type = namingStrategy.singularize(attribute.getName())+((EntityType<?>)attribute.getDeclaringType()).getName()+"Criteria";
261+
String type = namingStrategy.singularize(attribute.getName())+attribute.getDeclaringType().getJavaType().getSimpleName()+"Criteria";
290262

291-
if(whereAttributesMap.containsKey(type))
263+
if(whereAttributesMap.containsKey(type))
292264
return whereAttributesMap.get(type);
293265

294-
GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject()
266+
GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject()
295267
.name(type)
296268
.description("Criteria expression specification of "+namingStrategy.singularize(attribute.getName())+" attribute in entity " + attribute.getDeclaringType().getJavaType())
297269
.field(GraphQLInputObjectField.newInputObjectField()
@@ -422,6 +394,28 @@ private GraphQLArgument getArgument(Attribute<?,?> attribute) {
422394

423395
throw new IllegalArgumentException("Attribute " + attribute + " cannot be mapped as an Input Argument");
424396
}
397+
398+
private GraphQLObjectType getEmbeddableType(EmbeddableType<?> embeddableType) {
399+
if (embeddableCache.containsKey(embeddableType))
400+
return embeddableCache.get(embeddableType);
401+
402+
String embeddableTypeName = namingStrategy.singularize(embeddableType.getJavaType().getSimpleName())+"EmbeddableType";
403+
404+
GraphQLObjectType objectType = GraphQLObjectType.newObject()
405+
.name(embeddableTypeName)
406+
.description(getSchemaDescription( embeddableType.getJavaType()))
407+
.fields(embeddableType.getAttributes().stream()
408+
.filter(this::isNotIgnored)
409+
.map(this::getObjectField)
410+
.collect(Collectors.toList())
411+
)
412+
.build();
413+
414+
embeddableCache.putIfAbsent(embeddableType, objectType);
415+
416+
return objectType;
417+
}
418+
425419

426420
private GraphQLObjectType getObjectType(EntityType<?> entityType) {
427421
if (entityCache.containsKey(entityType))
@@ -465,6 +459,7 @@ private GraphQLFieldDefinition getObjectField(Attribute attribute) {
465459
// Get the fields that can be queried on (i.e. Simple Types, no Sub-Objects)
466460
if (attribute instanceof SingularAttribute
467461
&& attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC
462+
&& attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.EMBEDDED
468463
) {
469464

470465
EntityType foreignType = (EntityType) ((SingularAttribute) attribute).getType();
@@ -507,6 +502,10 @@ private GraphQLType getAttributeType(Attribute<?,?> attribute) {
507502
if (isBasic(attribute)) {
508503
return getGraphQLTypeFromJavaType(attribute.getJavaType());
509504
}
505+
else if (isEmbeddable(attribute)) {
506+
EmbeddableType embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType();
507+
return getEmbeddableType(embeddableType);
508+
}
510509
else if (isToMany(attribute)) {
511510
EntityType foreignType = (EntityType) ((PluralAttribute) attribute).getElementType();
512511
return new GraphQLList(new GraphQLTypeReference(foreignType.getName()));
@@ -530,6 +529,10 @@ else if (isElementCollection(attribute)) {
530529
"Attribute could not be mapped to GraphQL: field '" + declaringMember + "' of entity class '"+ declaringType +"'");
531530
}
532531

532+
protected final boolean isEmbeddable(Attribute<?,?> attribute) {
533+
return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED;
534+
}
535+
533536
protected final boolean isBasic(Attribute<?,?> attribute) {
534537
return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC;
535538
}
@@ -571,6 +574,10 @@ private String getSchemaDescription(AnnotatedElement annotatedElement) {
571574
return null;
572575
}
573576

577+
private boolean isNotIgnored(EmbeddableType<?> attribute) {
578+
return isNotIgnored(attribute.getJavaType());
579+
}
580+
574581
private boolean isNotIgnored(Attribute<?,?> attribute) {
575582
return isNotIgnored(attribute.getJavaMember()) && isNotIgnored(attribute.getJavaType());
576583
}

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import javax.persistence.criteria.Predicate;
4646
import javax.persistence.criteria.Root;
4747
import javax.persistence.metamodel.Attribute;
48+
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
4849
import javax.persistence.metamodel.EntityType;
4950
import javax.persistence.metamodel.PluralAttribute;
5051
import javax.persistence.metamodel.SingularAttribute;
@@ -713,8 +714,10 @@ protected EntityGraph<?> buildEntityGraph(Field root) {
713714

714715
selections(root)
715716
.forEach(it -> {
716-
717-
if(hasSelectionSet(it) && hasNoArguments(it)) {
717+
if(hasSelectionSet(it)
718+
&& hasNoArguments(it)
719+
&& isManagedType(entityType.getAttribute(it.getName()))
720+
) {
718721
Subgraph<?> sg = entityGraph.addSubgraph(it.getName());
719722
buildSubgraph(it, sg);
720723
} else {
@@ -726,6 +729,12 @@ protected EntityGraph<?> buildEntityGraph(Field root) {
726729
return entityGraph;
727730
};
728731

732+
733+
protected final boolean isManagedType(Attribute<?,?> attribute) {
734+
return attribute.getPersistentAttributeType() != PersistentAttributeType.EMBEDDED
735+
&& attribute.getPersistentAttributeType() != PersistentAttributeType.BASIC
736+
&& attribute.getPersistentAttributeType() != PersistentAttributeType.ELEMENT_COLLECTION;
737+
}
729738

730739
protected final boolean hasNoArguments(Field field) {
731740
return !hasArguments(field);

graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ public void queryWithAlias() {
157157
public void queryForElementCollection() {
158158
//given
159159
String query = "{ Author(id: 1) { id name, phoneNumbers } }";
160-
//String query = "{ Author(id: 1) { id name } }";
161160

162161
String expected = "{Author={id=1, name=Leo Tolstoy, phoneNumbers=[1-123-1234, 1-123-5678]}}";
163162

@@ -167,6 +166,52 @@ public void queryForElementCollection() {
167166
// then
168167
assertThat(result.toString()).isEqualTo(expected);
169168
}
169+
170+
// https://github.com/introproventures/graphql-jpa-query/issues/30
171+
@Test
172+
public void queryForEntityWithMappedSuperclass() {
173+
//given
174+
String query = "{ Car(id: \"1\") { id brand } }";
175+
176+
String expected = "{Car={id=1, brand=Ford}}";
177+
178+
//when
179+
Object result = executor.execute(query).getData();
180+
181+
// then
182+
assertThat(result.toString()).isEqualTo(expected);
183+
}
184+
185+
// https://github.com/introproventures/graphql-jpa-query/issues/30
186+
@Test
187+
public void queryForEntityWithEmeddableType() {
188+
//given
189+
String query = "{ Boat(id: \"1\") { id engine { identification } } }";
190+
191+
String expected = "{Boat={id=1, engine={identification=12345}}}";
192+
193+
//when
194+
Object result = executor.execute(query).getData();
195+
196+
// then
197+
assertThat(result.toString()).isEqualTo(expected);
198+
}
199+
200+
// // TODO
201+
// @Test
202+
// public void queryForEntityWithEmeddableTypeAndWhere() {
203+
// //given
204+
// String query = "{ Boats { id engine(where: { identification: { EQ: \"12345\"}}) { identification } } }";
205+
//
206+
// String expected = "{Boats={select[id=1, engine={identification=12345}]}}";
207+
//
208+
// //when
209+
// Object result = executor.execute(query).getData();
210+
//
211+
// // then
212+
// assertThat(result.toString()).isEqualTo(expected);
213+
// }
214+
170215

171216

172217
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
package com.introproventures.graphql.jpa.query.schema.model.embedded;
3+
4+
import javax.persistence.Embedded;
5+
import javax.persistence.Entity;
6+
import javax.persistence.Id;
7+
8+
import lombok.Data;
9+
10+
@Entity(name = "Boat")
11+
@Data
12+
public class Boat {
13+
14+
@Id
15+
String id;
16+
17+
@Embedded
18+
Engine engine;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
package com.introproventures.graphql.jpa.query.schema.model.embedded;
3+
4+
import javax.persistence.Entity;
5+
6+
import lombok.Data;
7+
import lombok.EqualsAndHashCode;
8+
9+
@Entity(name = "Car")
10+
@Data
11+
@EqualsAndHashCode(callSuper=true)
12+
public class Car extends Vehicle {
13+
14+
String brand;
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.introproventures.graphql.jpa.query.schema.model.embedded;
2+
3+
import javax.persistence.Embeddable;
4+
5+
@Embeddable
6+
public class Engine {
7+
8+
String identification;
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.introproventures.graphql.jpa.query.schema.model.embedded;
2+
3+
import javax.persistence.Id;
4+
import javax.persistence.MappedSuperclass;
5+
6+
import lombok.Data;
7+
8+
@MappedSuperclass
9+
@Data
10+
public abstract class Vehicle {
11+
12+
@Id
13+
String id;
14+
15+
}

graphql-jpa-query-schema/src/test/resources/data.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,17 @@ insert into author_phone_numbers(phone_number, author_id) values
119119
('1-123-5678', 1),
120120
('4-123-1234', 4),
121121
('4-123-5678', 4);
122+
123+
-- Car
124+
insert into Car (id, brand) values
125+
(1, 'Ford'),
126+
(2, 'Cadillac'),
127+
(3, 'Toyota');
128+
129+
130+
-- Boat
131+
insert into Boat (id, identification) values
132+
(1, '12345'),
133+
(2, '23456'),
134+
(3, '34567');
135+

0 commit comments

Comments
 (0)