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 7c4949f34..0cab3d16b 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 @@ -471,18 +471,8 @@ private GraphQLFieldDefinition getObjectField(Attribute attribute) { Stream attributes = findBasicAttributes(foreignType.getAttributes()); // TODO fix page count query - // TODO fix argument bindings. Only static parameter bindings are supported due to GraphQL limitation arguments.add(getWhereArgument(foreignType)); - // Disabled Simple attribute filtering -// attributes.forEach(it -> { -// arguments.add(GraphQLArgument.newArgument() -// .name(it.getName()) -// .type((GraphQLInputType) getAttributeType(it)) -// .build() -// ); -// }); - } // Get Sub-Objects fields queries via DataFetcher else if (attribute instanceof PluralAttribute && (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY @@ -513,22 +503,24 @@ private Stream> findBasicAttributes(Collection> at @SuppressWarnings( "rawtypes" ) private GraphQLType getAttributeType(Attribute attribute) { - if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC) { - if (attribute.getJavaType().isEnum()) { - return getTypeFromJavaType(attribute.getJavaType()); - } else { - return JavaScalars.of(attribute.getJavaType()); - } - - } else if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY) { + + if (isBasic(attribute)) { + return getGraphQLTypeFromJavaType(attribute.getJavaType()); + } + else if (isToMany(attribute)) { EntityType foreignType = (EntityType) ((PluralAttribute) attribute).getElementType(); return new GraphQLList(new GraphQLTypeReference(foreignType.getName())); - } else if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE) { + } + else if (isToOne(attribute)) { EntityType foreignType = (EntityType) ((SingularAttribute) attribute).getType(); return new GraphQLTypeReference(foreignType.getName()); - } else if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + } + else if (isElementCollection(attribute)) { Type foreignType = ((PluralAttribute) attribute).getElementType(); - return new GraphQLList(getTypeFromJavaType(foreignType.getJavaType())); + + if(foreignType.getPersistenceType() == Type.PersistenceType.BASIC) { + return new GraphQLList(getGraphQLTypeFromJavaType(foreignType.getJavaType())); + } } final String declaringType = attribute.getDeclaringType().getJavaType().getName(); // fully qualified name of the entity class @@ -538,7 +530,26 @@ private GraphQLType getAttributeType(Attribute attribute) { "Attribute could not be mapped to GraphQL: field '" + declaringMember + "' of entity class '"+ declaringType +"'"); } - private boolean isValidInput(Attribute attribute) { + protected final boolean isBasic(Attribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC; + } + + protected final boolean isElementCollection(Attribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION; + } + + protected final boolean isToMany(Attribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY + || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY; + } + + protected final boolean isToOne(Attribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE + || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE; + } + + + protected final boolean isValidInput(Attribute attribute) { return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION; } @@ -586,7 +597,7 @@ private boolean isNotIgnored(AnnotatedElement annotatedElement) { } @SuppressWarnings( "unchecked" ) - private GraphQLType getTypeFromJavaType(Class clazz) { + private GraphQLType getGraphQLTypeFromJavaType(Class clazz) { if (clazz.isEnum()) { if (classCache.containsKey(clazz)) @@ -597,15 +608,15 @@ private GraphQLType getTypeFromJavaType(Class clazz) { for (Enum enumValue : ((Class>)clazz).getEnumConstants()) enumBuilder.value(enumValue.name(), ordinal++); - GraphQLType answer = enumBuilder.build(); - setNoOpCoercing(answer); + GraphQLType enumType = enumBuilder.build(); + setNoOpCoercing(enumType); - classCache.putIfAbsent(clazz, answer); + classCache.putIfAbsent(clazz, enumType); - return answer; + return enumType; } - return null; + return JavaScalars.of(clazz); } protected GraphQLInputType getFieldsEnumType(EntityType entityType) { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java index 3f7fc9718..24c24fb68 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutorTests.java @@ -32,8 +32,6 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.Assert; -import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; -import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; @@ -152,5 +150,23 @@ public void queryWithAlias() { //then: assertThat(result.toString()).isEqualTo(expected); } + + + // https://github.com/introproventures/graphql-jpa-query/issues/33 + @Test + public void queryForElementCollection() { + //given + String query = "{ Author(id: 1) { id name, phoneNumbers } }"; + //String query = "{ Author(id: 1) { id name } }"; + + String expected = "{Author={id=1, name=Leo Tolstoy, phoneNumbers=[1-123-1234, 1-123-5678]}}"; + + //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/schema/model/book/Author.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java new file mode 100644 index 000000000..d261415a0 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java @@ -0,0 +1,55 @@ +/* + * 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.schema.model.book; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Getter +@Setter +@ToString +@EqualsAndHashCode(exclude={"books","phoneNumbers"}) // Fixes NPE in Hibernate when initializing loaded collections #1 +public class Author { + @Id + Long id; + + String name; + + @OneToMany(mappedBy="author") + Collection books; + + @ElementCollection + @CollectionTable(name = "author_phone_numbers", joinColumns = @JoinColumn(name = "author_id")) + @Column(name = "phone_number") + private Set phoneNumbers = new HashSet<>(); + +} diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java new file mode 100644 index 000000000..295309624 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java @@ -0,0 +1,40 @@ +/* + * 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.schema.model.book; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import lombok.Data; + +@Data +@Entity +public class Book { + @Id + Long id; + + String title; + + @ManyToOne + Author author; + + @Enumerated(EnumType.STRING) + Genre genre; +} diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Genre.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Genre.java new file mode 100644 index 000000000..fde77d103 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/book/Genre.java @@ -0,0 +1,21 @@ +/* + * 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.schema.model.book; + +public enum Genre { + NOVEL, PLAY +} diff --git a/graphql-jpa-query-schema/src/test/resources/data.sql b/graphql-jpa-query-schema/src/test/resources/data.sql index 2e032aed2..3f00f6bdc 100644 --- a/graphql-jpa-query-schema/src/test/resources/data.sql +++ b/graphql-jpa-query-schema/src/test/resources/data.sql @@ -105,3 +105,17 @@ insert into character_appears_in (character_id, appears_in) values -- Things insert into thing (id, type) values ('2D1EBC5B7D2741979CF0E84451C5BBB1', 'Thing1'); + +-- Books +insert into author (id, name) values (1, 'Leo Tolstoy'); +insert into book (id, title, author_id, genre) values (2, 'War and Peace', 1, 'NOVEL'); +insert into book (id, title, author_id, genre) values (3, 'Anna Karenina', 1, 'NOVEL'); +insert into author (id, name) values (4, 'Anton Chekhov'); +insert into book (id, title, author_id, genre) values (5, 'The Cherry Orchard', 4, 'PLAY'); +insert into book (id, title, author_id, genre) values (6, 'The Seagull', 4, 'PLAY'); +insert into book (id, title, author_id, genre) values (7, 'Three Sisters', 4, 'PLAY'); +insert into author_phone_numbers(phone_number, author_id) values + ('1-123-1234', 1), + ('1-123-5678', 1), + ('4-123-1234', 4), + ('4-123-5678', 4);