diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dc82b018..6102840ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,25 @@
# Change Log
-## 0.3.28-SNAPSHOT
+## 0.3.34-SNAPSHOT
+* fix(GH-193): Fixed error in GQL Queries when `total` or `pages` is included with condition `where: NOT_EXISTS` or `EXISTS` (#201) [40a0e2d](https://github.com/introproventures/graphql-jpa-query/commit/40a0e2d844854f8888e3502d1d6434db9cb1dd7e)
+* fix(GH-198): adedd support for fetching optional element collections elements (#200) [5d99c3b](https://github.com/introproventures/graphql-jpa-query/commit/5d99c3b4629521ebee2788c6b877250c279c8bf2)
+* fix: Added support for binding orderBy argument as a variable (#195) [2a01382](https://github.com/introproventures/graphql-jpa-query/commit/2a0138237cb639427f169b18f6b6f159c430afac)
+* fix: removed EntityManager.clear() from GraphQLJpaQueryDataFetcher (#192) [ae25aba](https://github.com/introproventures/graphql-jpa-query/commit/ae25aba8cbcf4397f2f0fb2c79fa2a23d9697cb6)
+* chore: remove obsolete JpaQueryBuilder class (#190) [ae9ce62](https://github.com/introproventures/graphql-jpa-query/commit/ae9ce6203c6d1f3c9448dddfa2405c61057f0035)
+* fix: switch off lower(COLUMN_NAME) decoration on String equality check (#186) [9445f89](https://github.com/introproventures/graphql-jpa-query/commit/9445f8965cb9e23665f4dc4f6ecc88833157bd5f)
+* fix: coerce Zoned and Offset time to UTC [f2d4756](https://github.com/introproventures/graphql-jpa-query/commit/f2d475640aa36ecbb6592fa4e216aa645772e832)
+* fix: Javadoc errors [8284cbd](https://github.com/introproventures/graphql-jpa-query/commit/8284cbd84c80092b4e506fbffc5706d0bed8133d)
+* fix: support uppercase attribute names introspection (#173) [798b30d](https://github.com/introproventures/graphql-jpa-query/commit/798b30d7fd8978f2cf8819d2e52d8c355f689b8b)
+* fix: add query support for entities with `@EmbeddedId` attribute (#180) [6552cb8](https://github.com/introproventures/graphql-jpa-query/commit/6552cb8b749ab5e1254b614375ad44badbc43b9d)
+* fix: Error querying entity with @IdClass #176 (#179) [50df533](https://github.com/introproventures/graphql-jpa-query/commit/50df533b87cd174f3eeb45266e23adf7172327ce)
+* fix: Description is not inherited from the parent class (#169) [82be5d5](https://github.com/introproventures/graphql-jpa-query/commit/82be5d5e3773923119cc549052875e6f9e3ee050)
+* Add java arrays basic support (#171) [a5f72bc](https://github.com/introproventures/graphql-jpa-query/commit/a5f72bc2c359c72cf7b91d6098286a2c3a903b68)
+* feat: support transient field modifier with class hierarchy introspection (#168) [81a6a19](https://github.com/introproventures/graphql-jpa-query/commit/81a6a198a1eecafb6056ed8ea9752482d26ba589)
+* feat: add support for variable where criteria expressions (#162) [5385a41](https://github.com/introproventures/graphql-jpa-query/commit/5385a4147cba654b4f57f3c6fc7cb4d40009ca3a)
+* Add METHOD to the @GraphQLIgnore annotation target (#165) [8028d12](https://github.com/introproventures/graphql-jpa-query/commit/8028d129f70f497e38463ad18fe2900072129877)
+* feat: add conditional property to disable GraphQLController (#161) [4bf7596](https://github.com/introproventures/graphql-jpa-query/commit/4bf75966b31e9a7d7e931ad9fbd300fec109d02d)
+* Add LocalDate/LocalDateTime/OffsetDateTime/ZonedDateTime/Instant support for query and filter (#158) [a3f7877](https://github.com/introproventures/graphql-jpa-query/commit/a3f7877b1b80fc074c4677c67511c55413172ef1)
+* fix(versions): update Spring Boot to 2.1.7 (#159) [257a92d](https://github.com/introproventures/graphql-jpa-query/commit/257a92d58cd12ef67ec4134c0277967c72d8eb9e)
* feat: add EXISTS/NOT_EXISTS subquery logical where criteria expressions (#151) [85d2f3a](https://github.com/introproventures/graphql-jpa-query/commit/85d2f3a7b64db6d54300f6979f872b38d1738364)
* fix: correct element collection fetch sort order (#149) [5327a22](https://github.com/introproventures/graphql-jpa-query/commit/5327a226b6e86418612fbaeaf6fedc7cd3c51576)
* fix: add support for nested where criteria expressions (#148) [2065101](https://github.com/introproventures/graphql-jpa-query/commit/206510146783dde2446e21f9a2c06cf61dcedf69)
diff --git a/README.md b/README.md
index 6d0bb9857..5b5880129 100644
--- a/README.md
+++ b/README.md
@@ -6,16 +6,16 @@ GraphQL Query Api for JPA Entity Models [](https://mvnrepository.com/artifact/com.introproventures/graphql-jpa-query)
[](https://jitpack.io/#introproventures/graphql-jpa-query)
-GraphQL is a query language for Web APIs implemented by GraphQL Java [graphql-java 11.0](https://github.com/andimarek/graphql-java) runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
-
-JPA 2.1 (Java Persistence Annotation) is Java's standard solution to bridge the gap between object-oriented domain models and relational database systems.
-
GraphQL JPA Query library uses JPA 2.1 specification to derive and build GraphQL Apis using GraphQL Java for your JPA Entity Java Classes. It provides a powerfull JPA Query Schema Builder to generate GraphQL Schema using JPA EntityManager Api and instruments GraphQL Schema with JPA Query Data Fetchers that transform GraphQL queries into JPA queries on the fly.
-Your applications can now use GraphQL queries that smoothly follow references between JPA resources with flexible type safe criteria expressions and user-friendly SQL query syntax semantics i.e. query by page, where criteria expressions, select, order by etc.
+GraphQL is a query language for Web APIs implemented by GraphQL Java [graphql-java 11.0](https://github.com/andimarek/graphql-java) runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
+
+Your applications can now use GraphQL queries that smoothly follow references between JPA entities with flexible type safe criteria expressions and user-friendly SQL query syntax semantics i.e. query by page, where criteria expressions, select, order by etc.
While typical REST APIs require loading from multiple URLs, GraphQL APIs get all the data your app needs in a single request. Apps using GraphQL can be quick even on slow mobile network connections.
+JPA 2.1 (Java Persistence Annotation) is Java's standard solution to bridge the gap between object-oriented domain models and relational database systems.
+
GraphQL JPA Query creates a uniform query API across for your applications without being limited by a single data source. You can use it with multiple JPA compliant databases by instrumenting separate EntityManager for each DataSource and expose a single GraphQL Query Apis for your Web application domain using Spring Boot Auto Configuration magic.
Tested using JDK Versions
diff --git a/graphql-jpa-query-annotations/pom.xml b/graphql-jpa-query-annotations/pom.xml
index 0300d398d..310c94a5a 100644
--- a/graphql-jpa-query-annotations/pom.xml
+++ b/graphql-jpa-query-annotations/pom.xml
@@ -6,7 +6,7 @@
com.introproventures
graphql-jpa-query-dependencies
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-dependencies
diff --git a/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLIgnore.java b/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLIgnore.java
index 0a9eaf922..7003f4dbe 100644
--- a/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLIgnore.java
+++ b/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLIgnore.java
@@ -16,6 +16,7 @@
package com.introproventures.graphql.jpa.query.annotation;
import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -28,7 +29,7 @@
* @author Igor Dianov
*
*/
-@Target( { TYPE, FIELD })
+@Target( { TYPE, FIELD, METHOD })
@Retention(RUNTIME)
public @interface GraphQLIgnore {
}
diff --git a/graphql-jpa-query-autoconfigure/pom.xml b/graphql-jpa-query-autoconfigure/pom.xml
index d8b3ce3bd..0e47a05d5 100644
--- a/graphql-jpa-query-autoconfigure/pom.xml
+++ b/graphql-jpa-query-autoconfigure/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
graphql-jpa-query-autoconfigure
diff --git a/graphql-jpa-query-boot-starter/pom.xml b/graphql-jpa-query-boot-starter/pom.xml
index 7ffa745c0..6560dfa4d 100644
--- a/graphql-jpa-query-boot-starter/pom.xml
+++ b/graphql-jpa-query-boot-starter/pom.xml
@@ -7,7 +7,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
diff --git a/graphql-jpa-query-build/pom.xml b/graphql-jpa-query-build/pom.xml
index 39cddc549..2a2fca240 100644
--- a/graphql-jpa-query-build/pom.xml
+++ b/graphql-jpa-query-build/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query-dependencies
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-dependencies
graphql-jpa-query-build
@@ -15,7 +15,7 @@
org.springframework.boot
spring-boot-dependencies
- 2.1.3.RELEASE
+ 2.1.7.RELEASE
pom
import
diff --git a/graphql-jpa-query-dependencies/pom.xml b/graphql-jpa-query-dependencies/pom.xml
index 8af3d4e71..6709c8b19 100644
--- a/graphql-jpa-query-dependencies/pom.xml
+++ b/graphql-jpa-query-dependencies/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
..
graphql-jpa-query-dependencies
diff --git a/graphql-jpa-query-example-merge/pom.xml b/graphql-jpa-query-example-merge/pom.xml
index 18d66fbda..cf4d61ac6 100644
--- a/graphql-jpa-query-example-merge/pom.xml
+++ b/graphql-jpa-query-example-merge/pom.xml
@@ -7,7 +7,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
diff --git a/graphql-jpa-query-example-merge/src/main/resources/books.sql b/graphql-jpa-query-example-merge/src/main/resources/books.sql
index 928d6a3d5..50a35ec5f 100644
--- a/graphql-jpa-query-example-merge/src/main/resources/books.sql
+++ b/graphql-jpa-query-example-merge/src/main/resources/books.sql
@@ -7,3 +7,16 @@ insert into book (id, title, author_id, genre) values (5, 'The Cherry Orchard',
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 (id, name, genre) values (8, 'Igor Dianov', 'JAVA');
+
+insert into book_tags (book_id, tags) values (2, 'war'), (2, 'piece');
+insert into book_tags (book_id, tags) values (3, 'anna'), (3, 'karenina');
+insert into book_tags (book_id, tags) values (5, 'cherry'), (5, 'orchard');
+insert into book_tags (book_id, tags) values (6, 'seagull');
+insert into book_tags (book_id, tags) values (7, 'three'), (7, 'sisters');
+
+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);
+
\ No newline at end of file
diff --git a/graphql-jpa-query-example-model-books/pom.xml b/graphql-jpa-query-example-model-books/pom.xml
index 8e8d45a52..ff711c100 100644
--- a/graphql-jpa-query-example-model-books/pom.xml
+++ b/graphql-jpa-query-example-model-books/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
graphql-jpa-query-example-model-books
diff --git a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
index efb530e1d..ce8045270 100644
--- a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
+++ b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
@@ -17,22 +17,29 @@
package com.introproventures.graphql.jpa.query.schema.model.book;
import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
+import javax.persistence.Transient;
+import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
+import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder;
+
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@Entity
-@EqualsAndHashCode(exclude="author")
+@EqualsAndHashCode(exclude= {"author", "tags"})
public class Book {
@Id
Long id;
@@ -42,6 +49,10 @@ public class Book {
@GraphQLIgnoreOrder
@GraphQLIgnoreFilter
String description;
+
+ @ElementCollection(fetch = FetchType.LAZY)
+ @GraphQLDescription("A set of user-defined tags")
+ private Set tags = new LinkedHashSet<>();
@ManyToOne(fetch=FetchType.LAZY, optional = false)
Author author;
@@ -49,5 +60,11 @@ public class Book {
@Enumerated(EnumType.STRING)
Genre genre;
- Date publicationDate;
+ Date publicationDate;
+
+ @Transient
+ @GraphQLIgnore
+ public String getAuthorName(){
+ return author.getName();
+ }
}
diff --git a/graphql-jpa-query-example-model-starwars/pom.xml b/graphql-jpa-query-example-model-starwars/pom.xml
index 9a91f3620..68994082c 100644
--- a/graphql-jpa-query-example-model-starwars/pom.xml
+++ b/graphql-jpa-query-example-model-starwars/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
diff --git a/graphql-jpa-query-example-simple/pom.xml b/graphql-jpa-query-example-simple/pom.xml
index c9a9373af..d240d315e 100644
--- a/graphql-jpa-query-example-simple/pom.xml
+++ b/graphql-jpa-query-example-simple/pom.xml
@@ -7,7 +7,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
diff --git a/graphql-jpa-query-graphiql/pom.xml b/graphql-jpa-query-graphiql/pom.xml
index 6ace54d69..b0c5c492b 100644
--- a/graphql-jpa-query-graphiql/pom.xml
+++ b/graphql-jpa-query-graphiql/pom.xml
@@ -3,7 +3,7 @@
com.introproventures
graphql-jpa-query
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
graphql-jpa-query-graphiql
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/pom.xml b/graphql-jpa-query-schema/pom.xml
index 87d219ab8..b2fbee6df 100644
--- a/graphql-jpa-query-schema/pom.xml
+++ b/graphql-jpa-query-schema/pom.xml
@@ -5,7 +5,7 @@
com.introproventures
graphql-jpa-query-build
- 0.3.28-SNAPSHOT
+ 0.3.34-SNAPSHOT
../graphql-jpa-query-build
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java
new file mode 100644
index 000000000..73db9e4a0
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java
@@ -0,0 +1,115 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class AnnotationDescriptor {
+
+ private final Annotation annotation;
+
+ private final Class extends Annotation> annotationType;
+
+ private final ElementType[] elementTypes;
+
+ private final RetentionPolicy policy;
+
+ private final boolean isDocumented;
+
+ private final boolean isInherited;
+
+ public AnnotationDescriptor(A annotation) {
+ this.annotation = annotation;
+ annotationType = annotation.annotationType();
+
+ Target target = annotationType.getAnnotation(Target.class);
+ elementTypes = (target == null) ? ElementType.values() : target.value();
+
+ Retention retention = annotationType.getAnnotation(Retention.class);
+ policy = (retention == null) ? RetentionPolicy.CLASS : retention.value();
+
+ Documented documented = annotationType.getAnnotation(Documented.class);
+ isDocumented = (documented != null);
+
+ Inherited inherited = annotationType.getAnnotation(Inherited.class);
+ isInherited = (inherited != null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public A getAnnotation() {
+ return (A) annotation;
+ }
+
+ public Class extends Annotation> getAnnotationType() {
+ return annotationType;
+ }
+
+ public ElementType[] getElementTypes() {
+ return elementTypes;
+ }
+
+ public RetentionPolicy getPolicy() {
+ return policy;
+ }
+
+ public boolean isDocumented() {
+ return isDocumented;
+ }
+
+ public boolean isInherited() {
+ return isInherited;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AnnotationDescriptor [annotation=")
+ .append(annotation)
+ .append(", annotationType=")
+ .append(annotationType)
+ .append(", elementTypes=")
+ .append(Arrays.toString(elementTypes))
+ .append(", policy=")
+ .append(policy)
+ .append(", isDocumented=")
+ .append(isDocumented)
+ .append(", isInherited=")
+ .append(isInherited)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(elementTypes);
+ result = prime * result + Objects.hash(annotation, annotationType, isDocumented, isInherited, policy);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AnnotationDescriptor other = (AnnotationDescriptor) obj;
+ return Objects.equals(annotation, other.annotation)
+ && Objects.equals(annotationType, other.annotationType)
+ && Arrays.equals(elementTypes, other.elementTypes)
+ && isDocumented == other.isDocumented
+ && isInherited == other.isInherited
+ && policy == other.policy;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java
new file mode 100644
index 000000000..6c68267f8
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java
@@ -0,0 +1,100 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class Annotations {
+
+ protected final AnnotatedElement annotatedElement;
+
+ protected final Map, AnnotationDescriptor> annotationsMap;
+
+ // cache
+ private AnnotationDescriptor[] allAnnotations;
+
+ public Annotations(AnnotatedElement annotatedElement) {
+ this.annotatedElement = annotatedElement;
+ this.annotationsMap = inspectAnnotations();
+ }
+
+ private Map, AnnotationDescriptor> inspectAnnotations() {
+
+ Annotation[] annotations = ReflectionUtil.getAnnotation(annotatedElement);
+ if (ArrayUtil.isEmpty(annotations)) {
+ return null;
+ }
+
+ Map, AnnotationDescriptor> map = new LinkedHashMap<>(annotations.length);
+
+ for (Annotation annotation : annotations) {
+ map.put(annotation.annotationType(), new AnnotationDescriptor(annotation));
+ }
+
+ return map;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ if (annotationsMap == null) {
+ return null;
+ }
+
+ return annotationsMap.get(clazz);
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ if (annotationsMap == null) {
+ return null;
+ }
+
+ if (allAnnotations == null) {
+ AnnotationDescriptor[] allAnnotations = new AnnotationDescriptor[annotationsMap.size()];
+
+ int index = 0;
+ for (AnnotationDescriptor annotationDescriptor : annotationsMap.values()) {
+ allAnnotations[index] = annotationDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allAnnotations, new Comparator() {
+ @Override
+ public int compare(AnnotationDescriptor ad1, AnnotationDescriptor ad2) {
+ return ad1.getClass().getName().compareTo(ad2.getClass().getName());
+ }
+ });
+
+ this.allAnnotations = allAnnotations;
+ }
+
+ return allAnnotations;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Annotations [annotatedElement=").append(annotatedElement).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(annotatedElement);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Annotations other = (Annotations) obj;
+ return Objects.equals(annotatedElement, other.annotatedElement);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java
new file mode 100644
index 000000000..7c5a70d33
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java
@@ -0,0 +1,93 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.Array;
+
+public class ArrayUtil {
+
+ private static final int INDEX_NOT_FOUND = -1;
+
+ public static boolean isEmpty(Object array) {
+ if(array == null) {
+ return true;
+ }
+
+ // not an array
+ if(!array.getClass().isArray()) {
+ return false;
+ }
+
+ // check array length
+ return Array.getLength(array) == 0;
+ }
+
+ public static boolean isNotEmpty(Object array) {
+ return !isEmpty(array);
+ }
+
+ public static int indexOf(Object[] array, Object objectToFind) {
+ return indexOf(array, objectToFind, 0);
+ }
+
+ public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ if (objectToFind == null) {
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = startIndex; i < array.length; i++) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+
+ return INDEX_NOT_FOUND;
+ }
+
+ public static T[] addAll(T[] array1, T[] array2) {
+ if (array1 == null) {
+ return (T[]) clone(array2);
+ } else if (array2 == null) {
+ return (T[]) clone(array1);
+ }
+ @SuppressWarnings("unchecked")
+ T[] joinedArray = (T[]) Array.newInstance(array1.getClass().getComponentType(), array1.length + array2.length);
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ try {
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ } catch (ArrayStoreException ase) {
+ // Check if problem was due to incompatible types
+ /*
+ * We do this here, rather than before the copy because: - it would be a wasted check most of the time -
+ * safer, in case check turns out to be too strict
+ */
+ final Class> type1 = array1.getClass().getComponentType();
+ final Class> type2 = array2.getClass().getComponentType();
+ if (!type1.isAssignableFrom(type2)) {
+ throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of "
+ + type1.getName());
+ }
+ throw ase; // No, so rethrow original
+ }
+ return joinedArray;
+ }
+
+ public static T[] clone(T[] array) {
+ if (array == null) {
+ return null;
+ }
+
+ return (T[]) array.clone();
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java
new file mode 100644
index 000000000..b76246a02
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java
@@ -0,0 +1,107 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.beans.Introspector;
+import java.lang.reflect.Method;
+
+public abstract class BeanUtil {
+
+ public static final String METHOD_GET_PREFIX = "get";
+ public static final String METHOD_IS_PREFIX = "is";
+ public static final String METHOD_SET_PREFIX = "set";
+
+ public static String getBeanGetterName(Method method) {
+ if (method == null) {
+ return null;
+ }
+
+ int prefixLength = getBeanGetterPrefixLength(method);
+ if (prefixLength == 0) {
+ return null;
+ }
+
+ String methodName = method.getName().substring(prefixLength);
+ return Introspector.decapitalize(methodName);
+ }
+
+ private static int getBeanGetterPrefixLength(Method method) {
+ if (isObjectMethod(method)) {
+ return 0;
+ }
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_GET_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return 3;
+ }
+
+ if (methodName.startsWith(METHOD_IS_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return 2;
+ }
+
+ return 0;
+ }
+
+ public static boolean isBeanProperty(Method method) {
+ if (method == null || isObjectMethod(method)) {
+ return false;
+ }
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_GET_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return true;
+ }
+ if (methodName.startsWith(METHOD_IS_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return true;
+ }
+ if (methodName.startsWith(METHOD_SET_PREFIX) && paramTypes.length == 1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean isBeanSetter(Method method) {
+ return getBeanSetterPrefixLength(method) != 0;
+ }
+
+ private static int getBeanSetterPrefixLength(Method method) {
+ if (isObjectMethod(method)) {
+ return 0;
+ }
+ String methodName = method.getName();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_SET_PREFIX)) {
+ if (paramTypes.length == 1) {
+ return 3;
+ }
+ }
+ return 0;
+ }
+
+ public static String getBeanSetterName(Method method) {
+ if (method == null) {
+ return null;
+ }
+
+ int prefixLength = getBeanSetterPrefixLength(method);
+ if (prefixLength == 0) {
+ return null;
+ }
+
+ String methodName = method.getName().substring(prefixLength);
+ return Introspector.decapitalize(methodName);
+ }
+
+ public static boolean isBeanGetter(Method method) {
+ if (method == null) {
+ return false;
+ }
+
+ return getBeanGetterPrefixLength(method) != 0;
+ }
+
+ private static boolean isObjectMethod(Method method) {
+ return method.getDeclaringClass() == Object.class;
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java
new file mode 100644
index 000000000..252debdf6
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java
@@ -0,0 +1,273 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class ClassDescriptor {
+
+ protected final Class> type;
+ protected final boolean scanAccessible;
+ protected final boolean scanStatics;
+ protected final boolean extendedProperties;
+ protected final boolean includeFieldsAsProperties;
+ protected final String propertyFieldPrefix;
+ protected final Class>[] interfaces;
+ protected final Class>[] superclasses;
+ protected int usageCount;
+
+ private final boolean isArray;
+ private final boolean isMap;
+ private final boolean isList;
+ private final boolean isSet;
+ private final boolean isCollection;
+ private final Fields fields;
+ private final Methods methods;
+ private final Properties properties;
+ private final Constructors constructors;
+
+ private final Annotations annotations;
+
+ public ClassDescriptor(Class> type, boolean scanAccessible, boolean extendedProperties,
+ boolean includeFieldsAsProperties, boolean scanStatics, String propertyFieldPrefix) {
+ this.type = type;
+ this.scanAccessible = scanAccessible;
+ this.extendedProperties = extendedProperties;
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ this.scanStatics = scanStatics;
+
+ isArray = type.isArray();
+ isMap = Map.class.isAssignableFrom(type);
+ isList = List.class.isAssignableFrom(type);
+ isSet = Set.class.isAssignableFrom(type);
+ isCollection = Collection.class.isAssignableFrom(type);
+
+ interfaces = ClassUtil.getAllInterfacesAsArray(type);
+ superclasses = ClassUtil.getAllSuperclassesAsArray(type);
+
+ fields = new Fields(this);
+ methods = new Methods(this);
+ properties = new Properties(this);
+ constructors = new Constructors(this);
+
+ annotations = new Annotations(type);
+ }
+
+ public Class> getType() {
+ return type;
+ }
+
+ public boolean isScanAccessible() {
+ return scanAccessible;
+ }
+
+ public boolean isExtendedProperties() {
+ return extendedProperties;
+ }
+
+ public boolean isIncludeFieldsAsProperties() {
+ return includeFieldsAsProperties;
+ }
+
+ public String getPropertyFieldPrefix() {
+ return propertyFieldPrefix;
+ }
+
+ protected void increaseUsageCount() {
+ usageCount++;
+ }
+
+ public int getUsageCount() {
+ return usageCount;
+ }
+
+ public boolean isArray() {
+ return isArray;
+ }
+
+ public boolean isMap() {
+ return isMap;
+ }
+
+ public boolean isList() {
+ return isList;
+ }
+
+ public boolean isSet() {
+ return isSet;
+ }
+
+ public boolean isCollection() {
+ return isCollection;
+ }
+
+ protected Fields getFields() {
+ return fields;
+ }
+
+ public FieldDescriptor getFieldDescriptor(String name, boolean declared) {
+ FieldDescriptor fieldDescriptor = getFields().getFieldDescriptor(name);
+
+ if (fieldDescriptor != null) {
+ if (!fieldDescriptor.matchDeclared(declared)) {
+ return null;
+ }
+ }
+
+ return fieldDescriptor;
+ }
+
+ public FieldDescriptor[] getAllFieldDescriptors() {
+ return getFields().getAllFieldDescriptors();
+ }
+
+ protected Methods getMethods() {
+ return methods;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, boolean declared) {
+ MethodDescriptor methodDescriptor = getMethods().getMethodDescriptor(name);
+
+ if ((methodDescriptor != null) && methodDescriptor.matchDeclared(declared)) {
+ return methodDescriptor;
+ }
+
+ return methodDescriptor;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, Class>[] params, boolean declared) {
+ MethodDescriptor methodDescriptor = getMethods().getMethodDescriptor(name, params);
+
+ if ((methodDescriptor != null) && methodDescriptor.matchDeclared(declared)) {
+ return methodDescriptor;
+ }
+
+ return null;
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors(String name) {
+ return getMethods().getAllMethodDescriptors(name);
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors() {
+ return getMethods().getAllMethodDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // properties
+
+ protected Properties getProperties() {
+ return properties;
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(String name, boolean declared) {
+ PropertyDescriptor propertyDescriptor = getProperties().getPropertyDescriptor(name);
+
+ if ((propertyDescriptor != null) && propertyDescriptor.matchDeclared(declared)) {
+ return propertyDescriptor;
+ }
+
+ return null;
+ }
+
+ public PropertyDescriptor[] getAllPropertyDescriptors() {
+ return getProperties().getAllPropertyDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // constructors
+
+ protected Constructors getConstructors() {
+ return constructors;
+ }
+
+ public ConstructorDescriptor getDefaultCtorDescriptor(boolean declared) {
+ ConstructorDescriptor defaultConstructor = getConstructors().getDefaultCtor();
+
+ if ((defaultConstructor != null) && defaultConstructor.matchDeclared(declared)) {
+ return defaultConstructor;
+ }
+ return null;
+ }
+
+ public ConstructorDescriptor getConstructorDescriptor(Class>[] args, boolean declared) {
+ ConstructorDescriptor constructorDescriptor = getConstructors().getCtorDescriptor(args);
+
+ if ((constructorDescriptor != null) && constructorDescriptor.matchDeclared(declared)) {
+ return constructorDescriptor;
+ }
+ return null;
+ }
+
+ public ConstructorDescriptor[] getAllConstructorDescriptors() {
+ return getConstructors().getAllCtorDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // annotations
+
+ protected Annotations getAnnotations() {
+ return annotations;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ return annotations.getAnnotationDescriptor(clazz);
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ return annotations.getAllAnnotationDescriptors();
+ }
+
+ public Class>[] getAllInterfaces() {
+ return interfaces;
+ }
+
+ public Class>[] getAllSuperclasses() {
+ return superclasses;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ClassDescriptor [type=")
+ .append(type)
+ .append(", scanAccessible=")
+ .append(scanAccessible)
+ .append(", extendedProperties=")
+ .append(extendedProperties)
+ .append(", includeFieldsAsProperties=")
+ .append(includeFieldsAsProperties)
+ .append(", propertyFieldPrefix=")
+ .append(propertyFieldPrefix)
+ .append(", usageCount=")
+ .append(usageCount)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ClassDescriptor other = (ClassDescriptor) obj;
+ return Objects.equals(type, other.type);
+ }
+
+
+ public boolean isScanStatics() {
+ return scanStatics;
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java
new file mode 100644
index 000000000..c832b6293
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java
@@ -0,0 +1,107 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ClassIntrospector {
+
+ protected final Map, ClassDescriptor> cache = new LinkedHashMap<>();
+ protected final boolean scanAccessible;
+ protected final boolean enhancedProperties;
+ protected final boolean includeFieldsAsProperties;
+ protected final boolean scanStatics;
+ protected final String propertyFieldPrefix;
+
+ private ClassIntrospector(Builder builder) {
+ this.scanAccessible = builder.scanAccessible;
+ this.enhancedProperties = builder.enhancedProperties;
+ this.includeFieldsAsProperties = builder.includeFieldsAsProperties;
+ this.propertyFieldPrefix = builder.propertyFieldPrefix;
+ this.scanStatics = builder.scanStatics ;
+ }
+
+ public ClassIntrospector() {
+ this(true, true, true, true, null);
+ }
+
+ public ClassIntrospector(boolean scanAccessible,
+ boolean enhancedProperties,
+ boolean includeFieldsAsProperties,
+ boolean scanStatics,
+ String propertyFieldPrefix) {
+ this.scanAccessible = scanAccessible;
+ this.enhancedProperties = enhancedProperties;
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ this.scanStatics = scanStatics;
+ }
+
+ public ClassDescriptor introspect(Class> type) {
+ ClassDescriptor cd = cache.computeIfAbsent(type, this::getClassDescriptor);
+
+ cd.increaseUsageCount();
+
+ return cd;
+ }
+
+ private ClassDescriptor getClassDescriptor(Class> type) {
+ return new ClassDescriptor(type,
+ scanAccessible,
+ enhancedProperties,
+ includeFieldsAsProperties,
+ scanStatics,
+ propertyFieldPrefix);
+ }
+
+ /**
+ * Creates builder to build {@link ClassIntrospector}.
+ * @return created builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder to build {@link ClassIntrospector}.
+ */
+ public static final class Builder {
+
+ public boolean scanStatics = false;
+ private boolean scanAccessible = true;
+ private boolean enhancedProperties = true;
+ private boolean includeFieldsAsProperties = true;
+ private String propertyFieldPrefix = null;
+
+ private Builder() {
+ }
+
+ public Builder withScanAccessible(boolean scanAccessible) {
+ this.scanAccessible = scanAccessible;
+ return this;
+ }
+
+ public Builder withEnhancedProperties(boolean enhancedProperties) {
+ this.enhancedProperties = enhancedProperties;
+ return this;
+ }
+
+ public Builder withIncludeFieldsAsProperties(boolean includeFieldsAsProperties) {
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ return this;
+ }
+
+ public Builder withPropertyFieldPrefix(String propertyFieldPrefix) {
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ return this;
+ }
+
+ public Builder withScanStatics(boolean includeStatics) {
+ this.scanStatics = includeStatics;
+ return this;
+ }
+
+ public ClassIntrospector build() {
+ return new ClassIntrospector(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java
new file mode 100644
index 000000000..6ce510694
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java
@@ -0,0 +1,70 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClassUtil {
+
+ public static Class>[] getAllInterfacesAsArray(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ List> interfacesFound = new ArrayList<>();
+ getAllInterfaces(clazz, interfacesFound);
+
+ return interfacesFound.toArray(new Class>[0]);
+ }
+
+ private static void getAllInterfaces(Class> clazz, List> interfacesFound) {
+ while (clazz != null) {
+ Class>[] interfaces = clazz.getInterfaces();
+
+ for (int i = 0; i < interfaces.length; i++) {
+ if (!interfacesFound.contains(interfaces[i])) {
+ interfacesFound.add(interfaces[i]);
+ getAllInterfaces(interfaces[i], interfacesFound);
+ }
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ public static List> getAllInterfaces(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ List> interfacesFound = new ArrayList<>();
+ getAllInterfaces(clazz, interfacesFound);
+
+ return interfacesFound;
+ }
+ public static List> getAllSuperclasses(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ List> classes = new ArrayList<>();
+ Class> superclass = clazz.getSuperclass();
+ while (superclass != null && superclass != Object.class) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes;
+ }
+
+ public static Class>[] getAllSuperclassesAsArray(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ List> classes = new ArrayList<>();
+ Class> superclass = clazz.getSuperclass();
+ while (superclass != null && superclass != Object.class) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes.toArray(new Class>[0]);
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java
new file mode 100644
index 000000000..6481396b0
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java
@@ -0,0 +1,77 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class ConstructorDescriptor extends Descriptor {
+
+ protected final Constructor> constructor;
+ protected final Class>[] parameters;
+
+ public ConstructorDescriptor(ClassDescriptor classDescriptor, Constructor> constructor) {
+ super(classDescriptor, ReflectionUtil.isPublic(constructor));
+ this.constructor = constructor;
+ this.parameters = constructor.getParameterTypes();
+
+ annotations = new Annotations(constructor);
+
+ ReflectionUtil.forceAccess(constructor);
+ }
+
+ @Override
+ public String getName() {
+ return constructor.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return constructor.getDeclaringClass();
+ }
+
+ public Constructor> getConstructor() {
+ return constructor;
+ }
+
+ public Class>[] getParameters() {
+ return parameters;
+ }
+
+ public boolean isDefault() {
+ return parameters.length == 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ConstructorDescriptor [constructor=")
+ .append(constructor)
+ .append(", parameters=")
+ .append(Arrays.toString(parameters))
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(parameters);
+ result = prime * result + Objects.hash(constructor);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ConstructorDescriptor other = (ConstructorDescriptor) obj;
+ return Objects.equals(constructor, other.constructor)
+ && Arrays.equals(parameters, other.parameters);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java
new file mode 100644
index 000000000..7bc5f558a
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java
@@ -0,0 +1,106 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Constructors {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final ConstructorDescriptor[] allConstructors;
+ protected ConstructorDescriptor defaultConstructor;
+
+ public Constructors(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.allConstructors = inspectConstructors();
+ }
+
+ protected ConstructorDescriptor[] inspectConstructors() {
+ Class> type = classDescriptor.getType();
+ Constructor>[] ctors = type.getDeclaredConstructors();
+
+ ConstructorDescriptor[] allConstructors = new ConstructorDescriptor[ctors.length];
+
+ for (int i = 0; i < ctors.length; i++) {
+ Constructor> ctor = ctors[i];
+
+ ConstructorDescriptor ctorDescriptor = createCtorDescriptor(ctor);
+ allConstructors[i] = ctorDescriptor;
+
+ if (ctorDescriptor.isDefault()) {
+ defaultConstructor = ctorDescriptor;
+ }
+ }
+
+ return allConstructors;
+ }
+
+ protected ConstructorDescriptor createCtorDescriptor(Constructor> constructor) {
+ return new ConstructorDescriptor(classDescriptor, constructor);
+ }
+
+ public ConstructorDescriptor getDefaultCtor() {
+ return defaultConstructor;
+ }
+
+ public ConstructorDescriptor getCtorDescriptor(Class>...args) {
+ ctors: for (ConstructorDescriptor ctorDescriptor : allConstructors) {
+ Class>[] arg = ctorDescriptor.getParameters();
+
+ if (arg.length != args.length) {
+ continue;
+ }
+
+ for (int j = 0; j < arg.length; j++) {
+ if (arg[j] != args[j]) {
+ continue ctors;
+ }
+ }
+
+ return ctorDescriptor;
+ }
+ return null;
+ }
+
+ ConstructorDescriptor[] getAllCtorDescriptors() {
+ return allConstructors;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Constructors [classDescriptor=")
+ .append(classDescriptor)
+ .append(", allConstructors=")
+ .append(Arrays.toString(allConstructors))
+ .append(", defaultConstructor=")
+ .append(defaultConstructor)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(allConstructors);
+ result = prime * result + Objects.hash(classDescriptor, defaultConstructor);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Constructors other = (Constructors) obj;
+ return Arrays.equals(allConstructors, other.allConstructors)
+ && Objects.equals(classDescriptor, other.classDescriptor)
+ && Objects.equals(defaultConstructor, other.defaultConstructor);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java
new file mode 100644
index 000000000..f1a050858
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java
@@ -0,0 +1,77 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.annotation.Annotation;
+import java.util.Objects;
+
+public abstract class Descriptor {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final boolean isPublic;
+
+ protected Annotations annotations;
+
+ protected Descriptor(ClassDescriptor classDescriptor, boolean isPublic) {
+ this.classDescriptor = classDescriptor;
+ this.isPublic = isPublic;
+ }
+
+ public ClassDescriptor getClassDescriptor() {
+ return classDescriptor;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+ public boolean matchDeclared(boolean declared) {
+ if (!declared) {
+ return isPublic;
+ }
+
+ return true;
+ }
+
+ protected Annotations getAnnotations() {
+ return annotations;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ return annotations.getAnnotationDescriptor(clazz);
+ }
+
+ public A getAnnotation(Class clazz) {
+ AnnotationDescriptor annotationDescriptor = annotations.getAnnotationDescriptor(clazz);
+ if (annotationDescriptor == null) {
+ return null;
+ }
+
+ return annotationDescriptor.getAnnotation();
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ return annotations.getAllAnnotationDescriptors();
+ }
+
+ public abstract String getName();
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(annotations, classDescriptor, isPublic);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Descriptor other = (Descriptor) obj;
+ return Objects.equals(annotations, other.annotations)
+ && Objects.equals(classDescriptor, other.classDescriptor)
+ && isPublic == other.isPublic;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java
new file mode 100644
index 000000000..9a5504d6b
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java
@@ -0,0 +1,126 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Type;
+import java.util.Objects;
+
+public class FieldDescriptor extends Descriptor implements Getter, Setter {
+
+ protected final Field field;
+ protected final Type type;
+ protected final Class> rawType;
+ protected final Class> rawComponentType;
+ protected final Class> rawKeyComponentType;
+
+ public FieldDescriptor(ClassDescriptor classDescriptor, Field field) {
+ super(classDescriptor, ReflectionUtil.isPublic(field));
+ this.field = field;
+ this.type = field.getGenericType();
+ this.rawType = ReflectionUtil.getRawType(type, classDescriptor.getType());
+
+ Class>[] componentTypes = ReflectionUtil.getComponentTypes(type, classDescriptor.getType());
+ if (componentTypes != null) {
+ this.rawComponentType = componentTypes[componentTypes.length - 1];
+ this.rawKeyComponentType = componentTypes[0];
+ } else {
+ this.rawComponentType = null;
+ this.rawKeyComponentType = null;
+ }
+
+ annotations = new Annotations(field);
+
+ ReflectionUtil.forceAccess(field);
+ }
+
+ @Override
+ public String getName() {
+ return field.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return field.getDeclaringClass();
+ }
+
+ public Field getField() {
+ return field;
+ }
+
+ public Class> getRawType() {
+ return rawType;
+ }
+
+ public Class> getRawComponentType() {
+ return rawComponentType;
+ }
+
+ public Class> getRawKeyComponentType() {
+ return rawKeyComponentType;
+ }
+
+ public Class>[] resolveRawComponentTypes() {
+ return ReflectionUtil.getComponentTypes(type, classDescriptor.getType());
+ }
+
+ @Override
+ public Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException {
+ return field.get(target);
+ }
+
+ @Override
+ public Class> getGetterRawType() {
+ return getRawType();
+ }
+
+ @Override
+ public Class> getGetterRawComponentType() {
+ return getRawComponentType();
+ }
+
+ @Override
+ public Class> getGetterRawKeyComponentType() {
+ return getRawKeyComponentType();
+ }
+
+ @Override
+ public void invokeSetter(Object target, Object argument) throws IllegalAccessException {
+ field.set(target, argument);
+ }
+
+ @Override
+ public Class> getSetterRawType() {
+ return getRawType();
+ }
+
+ @Override
+ public Class> getSetterRawComponentType() {
+ return getRawComponentType();
+ }
+
+ @Override
+ public String toString() {
+ return classDescriptor.getType().getSimpleName() + '#' + field.getName();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(field, type);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ FieldDescriptor other = (FieldDescriptor) obj;
+ return Objects.equals(field, other.field) && Objects.equals(type, other.type);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java
new file mode 100644
index 000000000..d022667ed
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java
@@ -0,0 +1,84 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Fields {
+
+ public static final String SERIAL_VERSION_UID = "serialVersionUID";
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map fieldsMap;
+
+ // cache
+ private FieldDescriptor[] allFields;
+
+ public Fields(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.fieldsMap = inspectFields();
+ }
+
+ protected Map inspectFields() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ boolean scanStatics = classDescriptor.isScanStatics();
+ Class> type = classDescriptor.getType();
+
+ Field[] fields =
+ scanAccessible ? ReflectionUtil.getAccessibleFields(type) : ReflectionUtil.getAllFieldsOfClass(type);
+
+ Map map = new LinkedHashMap<>(fields.length);
+
+ for (Field field : fields) {
+ String fieldName = field.getName();
+
+ if (fieldName.equals(SERIAL_VERSION_UID)) {
+ continue;
+ }
+
+ if (!scanStatics && Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ map.put(fieldName, createFieldDescriptor(field));
+ }
+
+ return map;
+ }
+
+ protected FieldDescriptor createFieldDescriptor(Field field) {
+ return new FieldDescriptor(classDescriptor, field);
+ }
+
+ public FieldDescriptor getFieldDescriptor(String name) {
+ return fieldsMap.get(name);
+ }
+
+ public FieldDescriptor[] getAllFieldDescriptors() {
+ if (allFields == null) {
+ FieldDescriptor[] allFields = new FieldDescriptor[fieldsMap.size()];
+
+ int index = 0;
+ for (FieldDescriptor fieldDescriptor : fieldsMap.values()) {
+ allFields[index] = fieldDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allFields, new Comparator() {
+ @Override
+ public int compare(FieldDescriptor fd1, FieldDescriptor fd2) {
+ return fd1.getField().getName().compareTo(fd2.getField().getName());
+ }
+ });
+
+ this.allFields = allFields;
+ }
+
+ return allFields;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java
new file mode 100644
index 000000000..37da6e6f5
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java
@@ -0,0 +1,13 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+public interface Getter {
+ Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException;
+
+ Class> getGetterRawType();
+
+ Class> getGetterRawComponentType();
+
+ Class> getGetterRawKeyComponentType();
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java
new file mode 100644
index 000000000..64a5dc515
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java
@@ -0,0 +1,158 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class MethodDescriptor extends Descriptor implements Getter, Setter {
+
+ protected final Method method;
+ protected final Type returnType;
+ protected final Class> rawReturnType;
+ protected final Class> rawReturnComponentType;
+ protected final Class> rawReturnKeyComponentType;
+ protected final Class>[] rawParameterTypes;
+ protected final Class>[] rawParameterComponentTypes;
+
+ public MethodDescriptor(ClassDescriptor classDescriptor, Method method) {
+ super(classDescriptor, ReflectionUtil.isPublic(method));
+ this.method = method;
+ this.returnType = method.getGenericReturnType();
+ this.rawReturnType = ReflectionUtil.getRawType(returnType, classDescriptor.getType());
+
+ Class>[] componentTypes = ReflectionUtil.getComponentTypes(returnType, classDescriptor.getType());
+ if (componentTypes != null) {
+ this.rawReturnComponentType = componentTypes[componentTypes.length - 1];
+ this.rawReturnKeyComponentType = componentTypes[0];
+ } else {
+ this.rawReturnComponentType = null;
+ this.rawReturnKeyComponentType = null;
+ }
+
+ annotations = new Annotations(method);
+
+ ReflectionUtil.forceAccess(method);
+
+ Type[] params = method.getGenericParameterTypes();
+ Type[] genericParams = method.getGenericParameterTypes();
+
+ rawParameterTypes = new Class[params.length];
+ rawParameterComponentTypes = genericParams.length == 0 ? null : new Class[params.length];
+
+ for (int i = 0; i < params.length; i++) {
+ Type type = params[i];
+ rawParameterTypes[i] = ReflectionUtil.getRawType(type, classDescriptor.getType());
+ if (rawParameterComponentTypes != null) {
+ rawParameterComponentTypes[i] =
+ ReflectionUtil.getComponentType(genericParams[i], classDescriptor.getType());
+ }
+ }
+ }
+
+ @Override
+ public String getName() {
+ return method.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return method.getDeclaringClass();
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public Class> getRawReturnType() {
+ return rawReturnType;
+ }
+
+ public Class> getRawReturnComponentType() {
+ return rawReturnComponentType;
+ }
+
+ public Class> getRawReturnKeyComponentType() {
+ return rawReturnKeyComponentType;
+ }
+
+ public Class>[] resolveRawReturnComponentTypes() {
+ return ReflectionUtil.getComponentTypes(returnType, classDescriptor.getType());
+ }
+
+ public Class>[] getRawParameterTypes() {
+ return rawParameterTypes;
+ }
+
+ public Class>[] getRawParameterComponentTypes() {
+ return rawParameterComponentTypes;
+ }
+
+ @Override
+ public Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException {
+ return method.invoke(target);
+ }
+
+ @Override
+ public Class> getGetterRawType() {
+ return getRawReturnType();
+ }
+
+ @Override
+ public Class> getGetterRawComponentType() {
+ return getRawReturnComponentType();
+ }
+
+ @Override
+ public Class> getGetterRawKeyComponentType() {
+ return getRawReturnKeyComponentType();
+ }
+
+ @Override
+ public void invokeSetter(Object target, Object argument) throws IllegalAccessException, InvocationTargetException {
+ method.invoke(target, argument);
+ }
+
+ @Override
+ public Class> getSetterRawType() {
+ return getRawParameterTypes()[0];
+ }
+
+ @Override
+ public Class> getSetterRawComponentType() {
+ Class>[] ts = getRawParameterComponentTypes();
+ if (ts == null) {
+ return null;
+ }
+ return ts[0];
+ }
+
+ @Override
+ public String toString() {
+ return classDescriptor.getType().getSimpleName() + '#' + method.getName() + "()";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(rawParameterTypes);
+ result = prime * result + Objects.hash(method, returnType);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MethodDescriptor other = (MethodDescriptor) obj;
+ return Objects.equals(method, other.method) && Arrays.equals(rawParameterTypes,
+ other.rawParameterTypes) && Objects.equals(returnType,
+ other.returnType);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java
new file mode 100644
index 000000000..820a560e9
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java
@@ -0,0 +1,116 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Methods {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map methodsMap;
+
+ // cached
+ private MethodDescriptor[] allMethods;
+
+ public Methods(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.methodsMap = inspectMethods();
+ }
+
+ protected Map inspectMethods() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ boolean scanStatics = classDescriptor.isScanStatics();
+ Class> type = classDescriptor.getType();
+
+ Method[] methods =
+ scanAccessible ? ReflectionUtil.getAccessibleMethods(type) : ReflectionUtil.getAllMethodsOfClass(type);
+
+ Map map = new LinkedHashMap<>(methods.length);
+
+ for (Method method : methods) {
+
+ if(!scanStatics && Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+
+ String methodName = method.getName();
+
+ MethodDescriptor[] mds = map.get(methodName);
+
+ if (mds == null) {
+ mds = new MethodDescriptor[1];
+ } else {
+ mds = Arrays.copyOf(mds, mds.length + 1);
+ }
+
+ map.put(methodName, mds);
+
+ mds[mds.length - 1] = createMethodDescriptor(method);
+ }
+
+ return map;
+ }
+
+ protected MethodDescriptor createMethodDescriptor(Method method) {
+ return new MethodDescriptor(classDescriptor, method);
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, Class>[] paramTypes) {
+ MethodDescriptor[] methodDescriptors = methodsMap.get(name);
+ if (methodDescriptors == null) {
+ return null;
+ }
+ for (int i = 0; i < methodDescriptors.length; i++) {
+ Method method = methodDescriptors[i].getMethod();
+ if (ObjectUtil.isEquals(method.getParameterTypes(), paramTypes)) {
+ return methodDescriptors[i];
+ }
+ }
+ return null;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name) {
+ MethodDescriptor[] methodDescriptors = methodsMap.get(name);
+ if (methodDescriptors == null) {
+ return null;
+ }
+ if (methodDescriptors.length != 1) {
+ throw new IllegalArgumentException("Method name not unique: " + name);
+ }
+ return methodDescriptors[0];
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors(String name) {
+ return methodsMap.get(name);
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors() {
+ if (allMethods == null) {
+ List allMethodsList = new ArrayList<>();
+
+ for (MethodDescriptor[] methodDescriptors : methodsMap.values()) {
+ Collections.addAll(allMethodsList, methodDescriptors);
+ }
+
+ MethodDescriptor[] allMethods = allMethodsList.toArray(new MethodDescriptor[allMethodsList.size()]);
+
+ Arrays.sort(allMethods, new Comparator() {
+ @Override
+ public int compare(MethodDescriptor md1, MethodDescriptor md2) {
+ return md1.getMethod().getName().compareTo(md2.getMethod().getName());
+ }
+ });
+
+ this.allMethods = allMethods;
+ }
+ return allMethods;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java
new file mode 100644
index 000000000..615109381
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java
@@ -0,0 +1,60 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public class ObjectUtil {
+
+ public static boolean isAnyNull(Object... objects) {
+ if(objects == null) {
+ return true;
+ }
+
+ return Stream.of(objects).anyMatch(Objects::isNull);
+ }
+
+ public static boolean isEquals(Object object1, Object object2) {
+ if (object1 == object2) {
+ return true;
+ }
+
+ if (object1 == null || object2 == null) {
+ return false;
+ }
+
+ if (!object1.getClass().equals(object2.getClass())) {
+ return false;
+ }
+
+ if (object1 instanceof Object[]) {
+ return Arrays.deepEquals((Object[]) object1, (Object[]) object2);
+ }
+ if (object1 instanceof int[]) {
+ return Arrays.equals((int[]) object1, (int[]) object2);
+ }
+ if (object1 instanceof long[]) {
+ return Arrays.equals((long[]) object1, (long[]) object2);
+ }
+ if (object1 instanceof short[]) {
+ return Arrays.equals((short[]) object1, (short[]) object2);
+ }
+ if (object1 instanceof byte[]) {
+ return Arrays.equals((byte[]) object1, (byte[]) object2);
+ }
+ if (object1 instanceof double[]) {
+ return Arrays.equals((double[]) object1, (double[]) object2);
+ }
+ if (object1 instanceof float[]) {
+ return Arrays.equals((float[]) object1, (float[]) object2);
+ }
+ if (object1 instanceof char[]) {
+ return Arrays.equals((char[]) object1, (char[]) object2);
+ }
+ if (object1 instanceof boolean[]) {
+ return Arrays.equals((boolean[]) object1, (boolean[]) object2);
+ }
+ return object1.equals(object2);
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java
new file mode 100644
index 000000000..9a827f810
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java
@@ -0,0 +1,175 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Properties {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map propertyDescriptors;
+
+ // cache
+ private PropertyDescriptor[] allProperties;
+
+ public Properties(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.propertyDescriptors = inspectProperties();
+ }
+
+ protected Map inspectProperties() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ Class> type = classDescriptor.getType();
+
+ Map map = new LinkedHashMap<>();
+
+ Method[] methods =
+ scanAccessible ? ReflectionUtil.getAccessibleMethods(type) : ReflectionUtil.getAllMethodsOfClass(type);
+
+ for (int iteration = 0; iteration < 2; iteration++) {
+ // first find the getters, and then the setters!
+ for (Method method : methods) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ continue; // ignore static methods
+ }
+
+ boolean add = false;
+ boolean issetter = false;
+
+ String propertyName;
+
+ if (iteration == 0) {
+ propertyName = BeanUtil.getBeanGetterName(method);
+ if (propertyName != null) {
+ add = true;
+ issetter = false;
+ }
+ } else {
+ propertyName = BeanUtil.getBeanSetterName(method);
+ if (propertyName != null) {
+ add = true;
+ issetter = true;
+ }
+ }
+
+ if (add == true) {
+ MethodDescriptor methodDescriptor =
+ classDescriptor.getMethodDescriptor(method.getName(), method.getParameterTypes(), true);
+ addProperty(map, propertyName, methodDescriptor, issetter);
+ }
+ }
+ }
+
+ if (classDescriptor.isIncludeFieldsAsProperties()) {
+ FieldDescriptor[] fieldDescriptors = classDescriptor.getAllFieldDescriptors();
+ String prefix = classDescriptor.getPropertyFieldPrefix();
+
+ for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
+ String name = fieldDescriptor.getField().getName();
+
+ if (prefix != null) {
+ if (!name.startsWith(prefix)) {
+ continue;
+ }
+ name = name.substring(prefix.length());
+ }
+
+ if (!map.containsKey(name)) {
+ // add missing field as a potential property
+ map.put(name, createPropertyDescriptor(name, fieldDescriptor));
+ }
+ }
+
+ }
+
+ return map;
+ }
+
+ protected void addProperty(Map map, String name, MethodDescriptor methodDescriptor,
+ boolean isSetter) {
+ MethodDescriptor setterMethod = isSetter ? methodDescriptor : null;
+ MethodDescriptor getterMethod = isSetter ? null : methodDescriptor;
+
+ PropertyDescriptor existing = map.get(name);
+
+ if (existing == null) {
+ // new property, just add it
+ PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod);
+
+ map.put(name, propertyDescriptor);
+ return;
+ }
+
+ if (!isSetter) {
+ // use existing setter
+ setterMethod = existing.getWriteMethodDescriptor();
+ // check existing
+ MethodDescriptor existingMethodDescriptor = existing.getReadMethodDescriptor();
+ if (existingMethodDescriptor != null) {
+ // check for special case of double get/is
+
+ // getter with the same name already exist
+ String methodName = methodDescriptor.getMethod().getName();
+ String existingMethodName = existingMethodDescriptor.getMethod().getName();
+
+ if (existingMethodName.startsWith(BeanUtil.METHOD_IS_PREFIX)
+ && methodName.startsWith(BeanUtil.METHOD_GET_PREFIX)) {
+ return;
+ }
+ }
+ } else {
+ // setter
+ // use existing getter
+ getterMethod = existing.getReadMethodDescriptor();
+
+ if (getterMethod.getMethod().getReturnType() != setterMethod.getMethod().getParameterTypes()[0]) {
+ return;
+ }
+ }
+
+ PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod);
+
+ map.put(name, propertyDescriptor);
+ }
+
+ protected PropertyDescriptor createPropertyDescriptor(String name, MethodDescriptor getterMethod,
+ MethodDescriptor setterMethod) {
+ return new PropertyDescriptor(classDescriptor, name, getterMethod, setterMethod);
+ }
+
+ protected PropertyDescriptor createPropertyDescriptor(String name, FieldDescriptor fieldDescriptor) {
+ return new PropertyDescriptor(classDescriptor, name, fieldDescriptor);
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(String name) {
+ return propertyDescriptors.get(name);
+ }
+
+ public PropertyDescriptor[] getAllPropertyDescriptors() {
+ if (allProperties == null) {
+ PropertyDescriptor[] allProperties = new PropertyDescriptor[propertyDescriptors.size()];
+
+ int index = 0;
+ for (PropertyDescriptor propertyDescriptor : propertyDescriptors.values()) {
+ allProperties[index] = propertyDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allProperties, new Comparator() {
+ @Override
+ public int compare(PropertyDescriptor pd1, PropertyDescriptor pd2) {
+ return pd1.getName().compareTo(pd2.getName());
+ }
+ });
+
+ this.allProperties = allProperties;
+ }
+
+ return allProperties;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java
new file mode 100644
index 000000000..c8c380ec8
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java
@@ -0,0 +1,237 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class PropertyDescriptor extends Descriptor {
+
+ protected final String name;
+ protected final MethodDescriptor readMethodDescriptor;
+ protected final MethodDescriptor writeMethodDescriptor;
+ protected final FieldDescriptor fieldDescriptor;
+
+ protected Class> type;
+ protected Getter[] getters;
+ protected Setter[] setters;
+
+ public PropertyDescriptor(ClassDescriptor classDescriptor, String propertyName, FieldDescriptor fieldDescriptor) {
+ super(classDescriptor, false);
+ this.name = propertyName;
+ this.readMethodDescriptor = null;
+ this.writeMethodDescriptor = null;
+ this.fieldDescriptor = fieldDescriptor;
+ this.annotations = new Annotations(fieldDescriptor.getField());
+ }
+
+ public PropertyDescriptor(ClassDescriptor classDescriptor, String propertyName, MethodDescriptor readMethod,
+ MethodDescriptor writeMethod) {
+ super(classDescriptor, ((readMethod == null) || readMethod.isPublic())
+ & (writeMethod == null || writeMethod.isPublic()));
+ this.name = propertyName;
+ this.readMethodDescriptor = readMethod;
+ this.writeMethodDescriptor = writeMethod;
+
+ if (classDescriptor.isExtendedProperties()) {
+ this.fieldDescriptor = findField(propertyName);
+ if(fieldDescriptor != null) {
+ this.annotations = new Annotations(fieldDescriptor.getField());
+ }
+ } else {
+ this.fieldDescriptor = null;
+ if(readMethod != null) {
+ this.annotations = new Annotations(readMethod.getMethod());
+ }
+ else if(writeMethod != null) {
+ this.annotations = new Annotations(writeMethod.getMethod());
+ }
+ }
+
+
+ }
+
+ protected FieldDescriptor findField(String fieldName) {
+ String prefix = classDescriptor.getPropertyFieldPrefix();
+
+ if (prefix != null) {
+ fieldName = prefix + fieldName;
+ }
+
+ return classDescriptor.getFieldDescriptor(fieldName, true);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public MethodDescriptor getReadMethodDescriptor() {
+ return readMethodDescriptor;
+ }
+
+ public MethodDescriptor getWriteMethodDescriptor() {
+ return writeMethodDescriptor;
+ }
+
+ public FieldDescriptor getFieldDescriptor() {
+ return fieldDescriptor;
+ }
+
+ public boolean isFieldOnlyDescriptor() {
+ return (readMethodDescriptor == null) && (writeMethodDescriptor == null);
+ }
+
+ public Class> getType() {
+ if (type == null) {
+ if (readMethodDescriptor != null) {
+ type = readMethodDescriptor.getMethod().getReturnType();
+ } else if (writeMethodDescriptor != null) {
+ type = writeMethodDescriptor.getMethod().getParameterTypes()[0];
+ } else if (fieldDescriptor != null) {
+ type = fieldDescriptor.getField().getType();
+ }
+ }
+
+ return type;
+ }
+
+ public Getter getGetter(boolean declared) {
+ if (getters == null) {
+ getters = new Getter[] { createGetter(false), createGetter(true), };
+ }
+
+ return getters[declared ? 1 : 0];
+ }
+
+ protected Getter createGetter(boolean declared) {
+ if (readMethodDescriptor != null) {
+ if (readMethodDescriptor.matchDeclared(declared)) {
+ return readMethodDescriptor;
+ }
+ }
+ if (fieldDescriptor != null) {
+ if (fieldDescriptor.matchDeclared(declared)) {
+ return fieldDescriptor;
+ }
+ }
+
+ return null;
+ }
+
+ public Setter getSetter(boolean declared) {
+ if (setters == null) {
+ setters = new Setter[] { createSetter(false), createSetter(true), };
+ }
+
+ return setters[declared ? 1 : 0];
+ }
+
+ protected Setter createSetter(boolean declared) {
+ if (writeMethodDescriptor != null) {
+ if (writeMethodDescriptor.matchDeclared(declared)) {
+ return writeMethodDescriptor;
+ }
+ }
+ if (fieldDescriptor != null) {
+ if (fieldDescriptor.matchDeclared(declared)) {
+ return fieldDescriptor;
+ }
+ }
+
+ return null;
+ }
+
+ public Class> resolveKeyType(boolean declared) {
+ Class> keyType = null;
+
+ Getter getter = getGetter(declared);
+
+ if (getter != null) {
+ keyType = getter.getGetterRawKeyComponentType();
+ }
+
+ if (keyType == null) {
+ FieldDescriptor fieldDescriptor = getFieldDescriptor();
+
+ if (fieldDescriptor != null) {
+ keyType = fieldDescriptor.getRawKeyComponentType();
+ }
+ }
+
+ return keyType;
+ }
+
+ public Class> resolveComponentType(boolean declared) {
+ Class> componentType = null;
+
+ Getter getter = getGetter(declared);
+
+ if (getter != null) {
+ componentType = getter.getGetterRawComponentType();
+ }
+
+ if (componentType == null) {
+ FieldDescriptor fieldDescriptor = getFieldDescriptor();
+
+ if (fieldDescriptor != null) {
+ componentType = fieldDescriptor.getRawComponentType();
+ }
+ }
+
+ return componentType;
+ }
+
+ // add
+ public Field getField() {
+ Class> clazz = this.getClassDescriptor().getType();
+
+ return ReflectionUtil.getField(clazz, this.getName());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PropertyDescriptor [name=")
+ .append(name)
+ .append(", readMethodDescriptor=")
+ .append(readMethodDescriptor)
+ .append(", writeMethodDescriptor=")
+ .append(writeMethodDescriptor)
+ .append(", fieldDescriptor=")
+ .append(fieldDescriptor)
+ .append(", type=")
+ .append(type)
+ .append(", getters=")
+ .append(Arrays.toString(getters))
+ .append(", setters=")
+ .append(Arrays.toString(setters))
+ .append(", classDescriptor=")
+ .append(classDescriptor)
+ .append(", isPublic=")
+ .append(isPublic)
+ .append(", annotations=")
+ .append(annotations)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(name, type);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PropertyDescriptor other = (PropertyDescriptor) obj;
+ return Objects.equals(name, other.name) && Objects.equals(type, other.type);
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java
new file mode 100644
index 000000000..70aa1f361
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java
@@ -0,0 +1,635 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class ReflectionUtil {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);
+
+ public static Field[] getAllFieldsOfClass(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ return getAllFieldsOfClass0(clazz);
+ }
+
+ public static Method[] getAccessibleMethods(Class> clazz) {
+ return getAccessibleMethods(clazz, Object.class);
+ }
+
+ public static Method[] getAllMethodsOfClass(final Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ Method[] methods = null;
+ Class> itr = clazz;
+ while (itr != null && !itr.equals(Object.class)) {
+ methods = ArrayUtil.addAll(itr.getDeclaredMethods(), methods);
+ itr = itr.getSuperclass();
+ }
+ return methods;
+ }
+
+ public static Method[] getAccessibleMethods(Class> clazz, Class> limit) {
+ Package topPackage = clazz.getPackage();
+ List methodList = new ArrayList<>();
+ int topPackageHash = (topPackage == null) ? 0 : topPackage.hashCode();
+ boolean top = true;
+ do {
+ if (clazz == null) {
+ break;
+ }
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ if (Modifier.isVolatile(method.getModifiers())) {
+ continue;
+ }
+ if (top) {
+ methodList.add(method);
+ continue;
+ }
+ int modifier = method.getModifiers();
+ if (Modifier.isPrivate(modifier) || Modifier.isAbstract(modifier)) {
+ continue;
+ }
+
+ if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
+ addMethodIfNotExist(methodList, method);
+ continue;
+ }
+ // add super default methods from the same package
+ Package pckg = method.getDeclaringClass().getPackage();
+ int pckgHash = (pckg == null) ? 0 : pckg.hashCode();
+ if (pckgHash == topPackageHash) {
+ addMethodIfNotExist(methodList, method);
+ }
+ }
+ top = false;
+ } while ((clazz = clazz.getSuperclass()) != limit);
+
+ Method[] methods = new Method[methodList.size()];
+ for (int i = 0; i < methods.length; i++) {
+ methods[i] = methodList.get(i);
+ }
+ return methods;
+ }
+
+ private static void addMethodIfNotExist(List allMethods, Method newMethod) {
+ for (Method method : allMethods) {
+ if (ObjectUtil.isEquals(method, newMethod)) {
+ return;
+ }
+ }
+
+ allMethods.add(newMethod);
+ }
+
+ public static Field getField(Class> clazz, String fieldName) {
+ if (ObjectUtil.isAnyNull(clazz, fieldName)) {
+ return null;
+ }
+
+ return getField0(clazz, fieldName);
+ }
+
+ static Field getField0(Class> clazz, String fieldName) {
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ Field[] fields = itr.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+
+ itr = itr.getSuperclass();
+ }
+
+ return null;
+ }
+ public static Class> getComponentType(Type type, Class> implClass) {
+ Class>[] componentTypes = getComponentTypes(type, implClass);
+ if (componentTypes == null) {
+ return null;
+ }
+ return componentTypes[componentTypes.length - 1];
+ }
+
+ public static Class> getComponentType(Type type) {
+ return getComponentType(type, null);
+ }
+
+ static Field[] getAllFieldsOfClass0(Class> clazz) {
+ Field[] fields = null;
+
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ fields = ArrayUtil.addAll(itr.getDeclaredFields(), fields);
+ itr = itr.getSuperclass();
+ }
+
+ return fields;
+ }
+
+ public static boolean hasSuperClass(Class> clazz) {
+ return (clazz != null) && !clazz.equals(Object.class);
+ }
+
+ public static Annotation[] getAnnotation(AnnotatedElement annotatedElement) {
+ if (Objects.isNull(annotatedElement)) {
+ return null;
+ }
+
+ return annotatedElement.getAnnotations();
+ }
+
+ public static boolean isPublic(Member m) {
+ return m != null && Modifier.isPublic(m.getModifiers());
+ }
+
+ public static boolean isAccessible(Member m) {
+ return m != null && Modifier.isPublic(m.getModifiers());
+ }
+
+ public static void forceAccess(AccessibleObject object) {
+ if (object == null || object.isAccessible()) {
+ return;
+ }
+ try {
+ object.setAccessible(true);
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Class> getRawType(Type type) {
+ return getRawType(type, null);
+ }
+
+ public static Class> getRawType(Type type, Class> implClass) {
+ if (type == null) {
+ return null;
+ }
+
+ GenericType gt = GenericType.find(type);
+ if (gt != null) {
+ return gt.toRawType(type, implClass);
+ }
+
+ return null;
+
+ }
+
+ public static Class>[] getComponentTypes(Type type, Class> implClass) {
+ if (type == null) {
+ return null;
+ }
+
+ GenericType gt = GenericType.find(type);
+ if (gt != null) {
+ return gt.getComponentTypes(type, implClass);
+ }
+
+ return null;
+
+ }
+
+ public static Field[] getAccessibleFields(Class> clazz) {
+ return getAccessibleFields(clazz, Object.class);
+ }
+
+ public static Field[] getAccessibleFields(Class> clazz, Class> limit) {
+ if (clazz == null) {
+ return null;
+ }
+
+ Package topPackage = clazz.getPackage();
+ List fieldList = new ArrayList<>();
+ int topPackageHash = (topPackage == null) ? 0 : topPackage.hashCode();
+ boolean top = true;
+ do {
+ if (clazz == null) {
+ break;
+ }
+ Field[] declaredFields = clazz.getDeclaredFields();
+ for (Field field : declaredFields) {
+ if (top == true) { // add all top declared fields
+ fieldList.add(field);
+ continue;
+ }
+ int modifier = field.getModifiers();
+ if (Modifier.isPrivate(modifier)) {
+ continue;
+ }
+ if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
+ addFieldIfNotExist(fieldList, field);
+ continue;
+ }
+
+ // add super default methods from the same package
+ Package pckg = field.getDeclaringClass().getPackage();
+ int pckgHash = (pckg == null) ? 0 : pckg.hashCode();
+ if (pckgHash == topPackageHash) {
+ addFieldIfNotExist(fieldList, field);
+ }
+ }
+ top = false;
+ } while ((clazz = clazz.getSuperclass()) != limit);
+
+ Field[] fields = new Field[fieldList.size()];
+ for (int i = 0; i < fields.length; i++) {
+ fields[i] = fieldList.get(i);
+ }
+
+ return fields;
+ }
+
+ private static void addFieldIfNotExist(List allFields, Field newField) {
+ for (Field field : allFields) {
+ if (ObjectUtil.isEquals(field, newField)) {
+ return;
+ }
+ }
+
+ allFields.add(newField);
+ }
+
+
+ enum GenericType {
+
+ CLASS_TYPE {
+
+ @Override
+ Class> type() {
+ return Class.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ return (Class>) type;
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ Class> clazz = (Class>) type;
+ if (clazz.isArray()) {
+ return new Class[] { clazz.getComponentType() };
+ }
+ return null;
+ }
+ },
+ PARAMETERIZED_TYPE {
+
+ @Override
+ Class> type() {
+ return ParameterizedType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ ParameterizedType pType = (ParameterizedType) type;
+ return getRawType(pType.getRawType(), implClass);
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ ParameterizedType pt = (ParameterizedType) type;
+
+ Type[] generics = pt.getActualTypeArguments();
+
+ if (generics.length == 0) {
+ return null;
+ }
+
+ Class>[] types = new Class[generics.length];
+
+ for (int i = 0; i < generics.length; i++) {
+ types[i] = getRawType(generics[i], implClass);
+ }
+ return types;
+ }
+ },
+ WILDCARD_TYPE {
+
+ @Override
+ Class> type() {
+ return WildcardType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ WildcardType wType = (WildcardType) type;
+
+ Type[] lowerTypes = wType.getLowerBounds();
+ if (lowerTypes.length > 0) {
+ return getRawType(lowerTypes[0], implClass);
+ }
+
+ Type[] upperTypes = wType.getUpperBounds();
+ if (upperTypes.length != 0) {
+ return getRawType(upperTypes[0], implClass);
+ }
+
+ return Object.class;
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ return null;
+ }
+ },
+ GENERIC_ARRAY_TYPE {
+
+ @Override
+ Class> type() {
+ return GenericArrayType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ Type genericComponentType = ((GenericArrayType) type).getGenericComponentType();
+ Class> rawType = getRawType(genericComponentType, implClass);
+ // FIXME
+ return Array.newInstance(rawType, 0).getClass();
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ GenericArrayType gat = (GenericArrayType) type;
+
+ Class> rawType = getRawType(gat.getGenericComponentType(), implClass);
+ if (rawType == null) {
+ return null;
+ }
+
+ return new Class[] { rawType };
+ }
+ },
+ TYPE_VARIABLE {
+
+ @Override
+ Class> type() {
+ return TypeVariable.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ TypeVariable> varType = (TypeVariable>) type;
+ if (implClass != null) {
+ Type resolvedType = resolveVariable(varType, implClass);
+ if (resolvedType != null) {
+ return getRawType(resolvedType, null);
+ }
+ }
+ Type[] boundsTypes = varType.getBounds();
+ if (boundsTypes.length == 0) {
+ return Object.class;
+ }
+ return getRawType(boundsTypes[0], implClass);
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ return null;
+ }
+ };
+
+ abstract Class> toRawType(Type type, Class> implClass);
+
+ abstract Class> type();
+
+ abstract Class>[] getComponentTypes(Type type, Class> implClass);
+
+ static GenericType find(Type type) {
+ for (GenericType gt : GenericType.values()) {
+ if (gt.type().isInstance(type)) {
+ return gt;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ public static Type resolveVariable(TypeVariable> variable, final Class> implClass) {
+ final Class> rawType = getRawType(implClass, null);
+
+ int index = ArrayUtil.indexOf(rawType.getTypeParameters(), variable);
+ if (index >= 0) {
+ return variable;
+ }
+
+ final Class>[] interfaces = rawType.getInterfaces();
+ final Type[] genericInterfaces = rawType.getGenericInterfaces();
+
+ for (int i = 0; i <= interfaces.length; i++) {
+ Class> rawInterface;
+
+ if (i < interfaces.length) {
+ rawInterface = interfaces[i];
+ } else {
+ rawInterface = rawType.getSuperclass();
+ if (rawInterface == null) {
+ continue;
+ }
+ }
+
+ final Type resolved = resolveVariable(variable, rawInterface);
+ if (resolved instanceof Class || resolved instanceof ParameterizedType) {
+ return resolved;
+ }
+
+ if (resolved instanceof TypeVariable) {
+ final TypeVariable> typeVariable = (TypeVariable>) resolved;
+ index = ArrayUtil.indexOf(rawInterface.getTypeParameters(), typeVariable);
+
+ if (index < 0) {
+ throw new IllegalArgumentException("Invalid type variable:" + typeVariable);
+ }
+
+ final Type type = i < genericInterfaces.length ? genericInterfaces[i] : rawType.getGenericSuperclass();
+
+ if (type instanceof Class) {
+ return Object.class;
+ }
+
+ if (type instanceof ParameterizedType) {
+ return ((ParameterizedType) type).getActualTypeArguments()[index];
+ }
+
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ }
+ return null;
+ }
+
+ public static Method getMethod(Class> clazz, String methodName, Class>...parameterTypes) {
+ if (clazz == null || methodName == null) {
+ return null;
+ }
+
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ Method[] methods = itr.getDeclaredMethods();
+
+ for (Method method : methods) {
+ if (method.getName().equals(methodName) && Arrays.equals(method.getParameterTypes(), parameterTypes)) {
+ return method;
+ }
+ }
+
+ itr = itr.getSuperclass();
+ }
+
+ return null;
+
+ }
+
+ public static Field[] getAllInstanceFields(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ return getAllInstanceFields0(clazz);
+ }
+
+ static Field[] getAllInstanceFields0(Class> clazz) {
+ List fields = new ArrayList<>();
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ for (Field field : itr.getDeclaredFields()) {
+ if (!Modifier.isStatic(field.getModifiers())) {
+ fields.add(field);
+ }
+ }
+ itr = itr.getSuperclass();
+ }
+
+ return fields.toArray(new Field[fields.size()]);
+ }
+
+ public static List getAnnotationMethods(Class clazz, Class annotationType) {
+ if (clazz == null || annotationType == null) {
+ return null;
+ }
+ List list = new ArrayList<>();
+
+ for (Method method : getAllMethodsOfClass(clazz)) {
+ A type = method.getAnnotation(annotationType);
+ if (type != null) {
+ list.add(method);
+ }
+ }
+
+ return list;
+ }
+
+ public static Field[] getAnnotationFields(Class> clazz, Class extends Annotation> annotationClass) {
+ if (clazz == null || annotationClass == null) {
+ return null;
+ }
+
+ Field[] fields = getAllFieldsOfClass0(clazz);
+ if (ArrayUtil.isEmpty(fields)) {
+ return null;
+ }
+
+ List list = new ArrayList<>();
+ for (Field field : fields) {
+ if (null != field.getAnnotation(annotationClass)) {
+ list.add(field);
+ field.setAccessible(true);
+ }
+ }
+
+ return list.toArray(new Field[0]);
+ }
+
+ public static Class>[] getGenericSuperTypes(Class> type) {
+ if (type == null) {
+ return null;
+ }
+
+ return getComponentTypes(type.getGenericSuperclass());
+ }
+
+ public static Class>[] getComponentTypes(Type type) {
+ return getComponentTypes(type, null);
+ }
+
+ public static T invokeMethod(Method method, Object target, Object...args) {
+ if (method == null) {
+ return null;
+ }
+
+ method.setAccessible(true);
+ try {
+ @SuppressWarnings("unchecked")
+ T result = (T) method.invoke(target, args);
+
+ return result;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+
+ public static Object invokeMethod(Object object, String methodName, Class>[] parameterTypes, Object...args) {
+ if (object == null || methodName == null) {
+ return null;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = new Class[0];
+ }
+ if (args == null) {
+ args = new Object[0];
+ }
+ Method method;
+ try {
+ method = object.getClass().getDeclaredMethod(methodName, parameterTypes);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ if (method == null) {
+ return null;
+ }
+
+ return invokeMethod(method, object, args);
+
+ }
+
+ public static Object invokeMethod(Object object, String methodName, Object...args) {
+ if (object == null || methodName == null) {
+ return null;
+ }
+ if (args == null) {
+ args = new Object[0];
+ }
+
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+
+ return invokeMethod(object, methodName, parameterTypes, args);
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java
new file mode 100644
index 000000000..2b14ba947
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java
@@ -0,0 +1,12 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+public interface Setter {
+
+ void invokeSetter(Object target, Object argument) throws IllegalAccessException, InvocationTargetException;
+
+ Class> getSetterRawType();
+
+ Class> getSetterRawComponentType();
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutor.java
index 6d0af186a..553485498 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutor.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/GraphQLExecutor.java
@@ -39,6 +39,7 @@ public interface GraphQLExecutor {
* Execute GraphQL query provided in query argument and variables
*
* @param query GraphQL query string
+ * @param arguments GraphQL arguments key/value mapo
* @return GraphQL ExecutionResult
*/
ExecutionResult execute(String query, Map arguments);
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/JavaScalars.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/JavaScalars.java
index faf6321c4..d2753080b 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/JavaScalars.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/JavaScalars.java
@@ -25,7 +25,10 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Collections;
@@ -38,6 +41,9 @@
import java.util.UUID;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import graphql.Assert;
import graphql.Scalars;
import graphql.language.ArrayValue;
@@ -55,8 +61,6 @@
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Provides Registry to resolve GraphQL Query Java Scalar Types
@@ -111,7 +115,10 @@ public class JavaScalars {
scalarsRegistry.put(Object.class, new GraphQLScalarType("Object", "Object type", new GraphQLObjectCoercing()));
scalarsRegistry.put(java.sql.Date.class, new GraphQLScalarType("SqlDate", "SQL Date type", new GraphQLSqlDateCoercing()));
scalarsRegistry.put(java.sql.Timestamp.class, new GraphQLScalarType("SqlTimestamp", "SQL Timestamp type", new GraphQLSqlTimestampCoercing()));
- scalarsRegistry.put(Byte[].class, new GraphQLScalarType("ByteArray", "ByteArray type", new GraphQLLOBCoercing()));
+ scalarsRegistry.put(Byte[].class, new GraphQLScalarType("ByteArray", "ByteArray type", new GraphQLLOBCoercing()));
+ scalarsRegistry.put(Instant.class, new GraphQLScalarType("Instant", "Instant type", new GraphQLInstantCoercing()));
+ scalarsRegistry.put(ZonedDateTime.class, new GraphQLScalarType("ZonedDateTime", "ZonedDateTime type", new GraphQLZonedDateTimeCoercing()));
+ scalarsRegistry.put(OffsetDateTime.class, new GraphQLScalarType("OffsetDateTime", "OffsetDateTime type", new GraphQLOffsetDateTimeCoercing()));
}
public static GraphQLScalarType of(Class> key) {
@@ -142,6 +149,8 @@ public Object serialize(Object input) {
return parseStringToLocalDateTime((String) input);
} else if (input instanceof LocalDateTime) {
return input;
+ }else if (input instanceof LocalDate) {
+ return input;
} else if (input instanceof Long) {
return parseLongToLocalDateTime((Long) input);
} else if (input instanceof Integer) {
@@ -331,6 +340,131 @@ private Date parseStringToDate(String input) {
}
};
+ public static class GraphQLInstantCoercing implements Coercing