├── src ├── test │ ├── resources │ │ ├── application-mps.yaml │ │ ├── application-cars.yaml │ │ ├── application-city.yaml │ │ ├── application-knows.yaml │ │ ├── application-world.yaml │ │ ├── application-social.yaml │ │ ├── application-traversal.yaml │ │ ├── application.yaml │ │ ├── knows.graphqls │ │ ├── mps.graphqls │ │ ├── traversal.graphqls │ │ ├── social.graphqls │ │ ├── world.graphqls │ │ ├── city.graphqls │ │ └── cars.graphqls │ └── java │ │ └── com │ │ └── arangodb │ │ └── graphql │ │ └── spring │ │ └── boot │ │ ├── TestApplication.java │ │ ├── DatabaseAutoCreateTest.java │ │ ├── DatabaseDisableAutoCreateTest.java │ │ └── CityWebTest.java └── main │ ├── resources │ ├── META-INF │ │ └── spring.factories │ └── __arango-graphql-directives.graphqls │ └── java │ └── com │ └── arangodb │ └── graphql │ └── spring │ └── boot │ └── autoconfigure │ ├── AutomaticDatabaseObjectCreator.java │ ├── ArangoAutoConfiguration.java │ ├── ArangoGraphQLAutoConfiguration.java │ └── ArangoGraphQLConfigurationProperties.java ├── .gitignore ├── pom.xml ├── LICENSE └── README.md /src/test/resources/application-mps.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: mps.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-cars.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: cars.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-city.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: city.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-knows.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: knows.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-world.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: world.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-social.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: social.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application-traversal.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | schemaLocation: traversal.graphqls 3 | -------------------------------------------------------------------------------- /src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | arangodb: 2 | hosts: 3 | user: 4 | password: 5 | useSsl: false 6 | database: _system 7 | 8 | logging: 9 | level: 10 | com.arangodb.graphql: trace -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | com.arangodb.graphql.spring.boot.autoconfigure.ArangoAutoConfiguration,\ 4 | com.arangodb.graphql.spring.boot.autoconfigure.ArangoGraphQLAutoConfiguration -------------------------------------------------------------------------------- /src/main/resources/__arango-graphql-directives.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @edgeTarget on FIELD_DEFINITION 3 | directive @vertex(collection : String!) on OBJECT 4 | directive @discriminator(property : String) on INTERFACE | UNION 5 | directive @alias(name : String) on OBJECT -------------------------------------------------------------------------------- /src/test/resources/knows.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | 4 | type Person @vertex(collection: "persons") { 5 | name: String! 6 | knows: [Person] @edge(collection: "knows", direction: "outbound") 7 | } 8 | 9 | type Query { 10 | findPerson(name: String!): [Person] 11 | } -------------------------------------------------------------------------------- /src/test/resources/mps.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | directive @edgeTarget on FIELD_DEFINITION 4 | 5 | type MpsVertex @vertex(collection: "mps_verts") { 6 | _key: String 7 | vertices: [MpsVertex] @edge(collection: "mps_edges", direction: "outbound") 8 | } 9 | 10 | 11 | type Query { 12 | findMpsVertex(_key: String!): [MpsVertex] 13 | } -------------------------------------------------------------------------------- /src/test/resources/traversal.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | directive @edgeTarget on FIELD_DEFINITION 4 | 5 | type Circle @vertex(collection: "circles") { 6 | _key: String 7 | label: String 8 | circles: [Edge]! @edge(collection: "edges", direction: "outbound") 9 | } 10 | 11 | type Edge { 12 | label: String! 13 | circle: Circle @edgeTarget 14 | } 15 | 16 | type Query { 17 | findCircle(_key: String!): [Circle] 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | 26 | target 27 | 28 | #idea-generated 29 | .idea 30 | *.iml 31 | 32 | #eclipse-generated 33 | .settings/ 34 | .classpath 35 | .project 36 | 37 | node_modules 38 | -------------------------------------------------------------------------------- /src/main/java/com/arangodb/graphql/spring/boot/autoconfigure/AutomaticDatabaseObjectCreator.java: -------------------------------------------------------------------------------- 1 | package com.arangodb.graphql.spring.boot.autoconfigure; 2 | 3 | import com.arangodb.ArangoDB; 4 | import com.arangodb.graphql.create.DatabaseObjectCreator; 5 | import graphql.GraphQL; 6 | 7 | import javax.annotation.PostConstruct; 8 | 9 | public class AutomaticDatabaseObjectCreator { 10 | 11 | private final DatabaseObjectCreator databaseObjectCreator; 12 | 13 | public AutomaticDatabaseObjectCreator(DatabaseObjectCreator databaseObjectCreator){ 14 | this.databaseObjectCreator = databaseObjectCreator; 15 | } 16 | 17 | @PostConstruct 18 | public void init(){ 19 | 20 | databaseObjectCreator.createDatabase(); 21 | databaseObjectCreator.createCollections(); 22 | databaseObjectCreator.createIndexes(); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/social.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | directive @edgeTarget on FIELD_DEFINITION 4 | 5 | interface SocialPerson { 6 | name: String! 7 | relations: [Relation] @edge(collection: "relation", direction: "outbound") 8 | } 9 | 10 | type Relation { 11 | type: String! 12 | to: SocialPerson @edgeTarget 13 | } 14 | 15 | type Female implements SocialPerson @vertex(collection: "female") { 16 | name: String! 17 | relations: [Relation] @edge(collection: "relation", direction: "outbound") 18 | } 19 | 20 | type Male implements SocialPerson @vertex(collection: "male") { 21 | name: String! 22 | relations: [Relation] @edge(collection: "relation", direction: "outbound") 23 | } 24 | 25 | 26 | type Query { 27 | 28 | findMale(name: String!): [Male] 29 | findFemale(name: String!): [Female] 30 | 31 | } -------------------------------------------------------------------------------- /src/test/resources/world.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | directive @discriminator(property : String) on INTERFACE | UNION 4 | directive @alias(name : String) on OBJECT 5 | directive @edgeTarget on FIELD_DEFINITION 6 | 7 | interface Location @discriminator(property: "type") { 8 | name: String 9 | } 10 | 11 | type Planet implements Location @vertex(collection: "worldVertices") @alias(name: "root") { 12 | name: String 13 | continents: [Continent] @edge(collection:"worldEdges", direction:"inbound") 14 | } 15 | 16 | type Continent implements Location @vertex(collection: "worldVertices") @alias(name: "continent") { 17 | name: String 18 | countries: [Country] @edge(collection:"worldEdges", direction:"inbound") 19 | } 20 | 21 | type Country implements Location @vertex(collection: "worldVertices") @alias(name: "country") { 22 | name: String! 23 | code: String 24 | capital: City @edge(collection:"worldEdges", direction:"inbound") 25 | } 26 | 27 | type City implements Location @vertex(collection: "worldVertices") @alias(name: "capital") { 28 | name: String 29 | } 30 | 31 | type Query { 32 | world: [Planet] 33 | } -------------------------------------------------------------------------------- /src/test/java/com/arangodb/graphql/spring/boot/TestApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * DISCLAIMER 3 | * 4 | * Copyright 2016 ArangoDB GmbH, Cologne, Germany 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | */ 20 | 21 | 22 | package com.arangodb.graphql.spring.boot; 23 | 24 | import org.springframework.boot.SpringApplication; 25 | import org.springframework.boot.autoconfigure.SpringBootApplication; 26 | 27 | /** 28 | * @author Michele Rastelli 29 | */ 30 | @SpringBootApplication 31 | public class TestApplication { 32 | public static void main(String[] args) { 33 | SpringApplication.run(TestApplication.class, args); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/arangodb/graphql/spring/boot/DatabaseAutoCreateTest.java: -------------------------------------------------------------------------------- 1 | package com.arangodb.graphql.spring.boot; 2 | 3 | import com.arangodb.graphql.create.DatabaseObjectCreator; 4 | import com.arangodb.graphql.spring.boot.autoconfigure.AutomaticDatabaseObjectCreator; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import static org.mockito.Mockito.verify; 15 | 16 | @RunWith(SpringRunner.class) 17 | public class DatabaseAutoCreateTest { 18 | 19 | @Configuration 20 | @Import(AutomaticDatabaseObjectCreator.class) // A @Component injected with ExampleService 21 | static class Config { 22 | } 23 | 24 | @MockBean 25 | private DatabaseObjectCreator databaseObjectCreator; 26 | 27 | @Test 28 | public void autoCreateInvoked(){ 29 | verify(databaseObjectCreator).createDatabase(); 30 | verify(databaseObjectCreator).createCollections(); 31 | verify(databaseObjectCreator).createIndexes();; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/city.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT 3 | directive @edgeTarget on FIELD_DEFINITION 4 | 5 | type Geometry { 6 | coordinates: [Float] 7 | } 8 | 9 | interface City { 10 | _key: String! 11 | geometry: Geometry 12 | isCapital: Boolean 13 | population: Int 14 | domesticDestination: [Destination] 15 | internationalDestinations: [Destination] 16 | } 17 | 18 | type Destination { 19 | distance: Int 20 | city: City @edgeTarget 21 | } 22 | 23 | type GermanCity implements City @vertex(collection: "germanCity") { 24 | _key: String! 25 | geometry: Geometry 26 | isCapital: Boolean 27 | population: Int 28 | domesticDestination: [Destination] @edge(collection:"germanHighway", direction:"outbound") 29 | internationalDestinations: [Destination] @edge(collection:"internationalHighway", direction:"outbound") 30 | } 31 | 32 | type FrenchCity implements City @vertex(collection: "frenchCity") { 33 | _key: String! 34 | geometry: Geometry 35 | isCapital: Boolean 36 | population: Int 37 | domesticDestination: [Destination] @edge(collection:"frenchHighway", direction:"outbound") 38 | internationalDestinations: [Destination] @edge(collection:"internationalHighway", direction:"outbound") 39 | } 40 | 41 | type Query { 42 | germany(_key: String): [GermanCity] 43 | france: [FrenchCity] 44 | } -------------------------------------------------------------------------------- /src/test/java/com/arangodb/graphql/spring/boot/DatabaseDisableAutoCreateTest.java: -------------------------------------------------------------------------------- 1 | package com.arangodb.graphql.spring.boot; 2 | 3 | import com.arangodb.graphql.create.DatabaseObjectCreator; 4 | import com.arangodb.graphql.spring.boot.autoconfigure.AutomaticDatabaseObjectCreator; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.boot.test.mock.mockito.MockBean; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.context.annotation.Import; 14 | import org.springframework.test.context.ActiveProfiles; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | 17 | import static org.junit.Assert.assertThat; 18 | import static org.mockito.ArgumentMatchers.isNull; 19 | import static org.mockito.Mockito.verify; 20 | 21 | @RunWith(SpringRunner.class) 22 | @ActiveProfiles("city") 23 | @SpringBootTest 24 | public class DatabaseDisableAutoCreateTest { 25 | 26 | @Autowired 27 | private ApplicationContext applicationContext; 28 | 29 | @Test(expected = NoSuchBeanDefinitionException.class) 30 | public void autoCreateDisabled() { 31 | applicationContext.getBean(AutomaticDatabaseObjectCreator.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/cars.graphqls: -------------------------------------------------------------------------------- 1 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 2 | directive @vertex(collection : String!) on OBJECT | INTERFACE 3 | directive @discriminator(property : String) on INTERFACE | UNION | OBJECT 4 | directive @alias(name : String) on OBJECT 5 | directive @edgeTarget on FIELD_DEFINITION 6 | 7 | type Owner @vertex(collection: "Owners") { 8 | _id: String! 9 | name: String! 10 | cars(make: String, model: String, fuel: String): [Ownership] @edge(collection: "ownership", direction: "outbound") 11 | } 12 | 13 | type Ownership { 14 | finance: String 15 | car: Car @edgeTarget 16 | } 17 | 18 | type OwnedBy { 19 | finance: String 20 | owner: Owner @edgeTarget 21 | } 22 | 23 | interface Car @vertex(collection: "Cars") @discriminator(property: "vehicleType") { 24 | _id: String! 25 | make: String! 26 | model: String! 27 | variant: String! 28 | owners: [OwnedBy] @edge(collection: "ownership", direction: "inbound") 29 | } 30 | 31 | type StandardCar implements Car @vertex(collection: "Cars") @alias(name: "standard") { 32 | _id: String! 33 | make: String! 34 | model: String! 35 | variant: String! 36 | owners: [OwnedBy] @edge(collection: "ownership", direction: "inbound") 37 | } 38 | 39 | type ConvertibleCar implements Car @vertex(collection: "Cars") @alias(name: "conv") { 40 | _id: String! 41 | make: String! 42 | model: String! 43 | variant: String! 44 | owners: [OwnedBy] @edge(collection: "ownership", direction: "inbound") 45 | roofType: String! 46 | } 47 | 48 | 49 | type Query { 50 | 51 | findOwner(name: String): [Owner] 52 | findCar(make: String, model: String): [Car] 53 | 54 | } -------------------------------------------------------------------------------- /src/test/java/com/arangodb/graphql/spring/boot/CityWebTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * DISCLAIMER 3 | * 4 | * Copyright 2016 ArangoDB GmbH, Cologne, Germany 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | */ 20 | 21 | 22 | package com.arangodb.graphql.spring.boot; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 28 | import org.springframework.boot.test.context.SpringBootTest; 29 | import org.springframework.test.context.ActiveProfiles; 30 | import org.springframework.test.context.junit4.SpringRunner; 31 | import org.springframework.test.web.servlet.MockMvc; 32 | import org.springframework.test.web.servlet.MvcResult; 33 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 34 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 35 | 36 | /** 37 | * @author Michele Rastelli 38 | */ 39 | @SpringBootTest 40 | @RunWith(SpringRunner.class) 41 | @AutoConfigureMockMvc 42 | @ActiveProfiles("city") 43 | public class CityWebTest { 44 | 45 | @Autowired 46 | private MockMvc mvc; 47 | 48 | @Test 49 | public void getFrenchCity() throws Exception { 50 | String query = "{ france { _key, isCapital } }"; 51 | MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/graphql").contentType("application/json") 52 | .content("{\"query\" : \"" + query + "\" }")).andExpect(MockMvcResultMatchers.status().isOk()) 53 | .andReturn(); 54 | 55 | String response = result.getResponse().getContentAsString(); 56 | System.out.println(response); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/main/java/com/arangodb/graphql/spring/boot/autoconfigure/ArangoAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * DISCLAIMER 3 | * Copyright 2019 ArangoDB GmbH, Cologne, Germany 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Copyright holder is ArangoDB GmbH, Cologne, Germany 18 | * 19 | */ 20 | package com.arangodb.graphql.spring.boot.autoconfigure; 21 | 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import com.arangodb.ArangoDB; 30 | import com.arangodb.ArangoDBException; 31 | 32 | 33 | /** 34 | * {@link EnableAutoConfiguration} class for ArangoDB 35 | * 36 | * Initial implementation borrowed from the Arango spring-data Spring Boot Starter 37 | * Adding Properties for GraphQL 38 | * 39 | * @author Mark Vollmary 40 | * @author Colin Findlay 41 | * 42 | */ 43 | @Configuration 44 | @EnableConfigurationProperties(ArangoGraphQLConfigurationProperties.class) 45 | public class ArangoAutoConfiguration { 46 | 47 | private final ArangoGraphQLConfigurationProperties properties; 48 | 49 | @Autowired 50 | public ArangoAutoConfiguration(final ArangoGraphQLConfigurationProperties properties) { 51 | this.properties = properties; 52 | } 53 | 54 | @Bean 55 | public ArangoDB.Builder arango() { 56 | final ArangoDB.Builder builder = new ArangoDB.Builder() 57 | .user(properties.getUser()) 58 | .password(properties.getPassword()) 59 | .timeout(properties.getTimeout()) 60 | .useSsl(properties.getUseSsl()) 61 | .maxConnections(properties.getMaxConnections()) 62 | .connectionTtl(properties.getConnectionTtl()) 63 | .acquireHostList(properties.getAcquireHostList()) 64 | .loadBalancingStrategy(properties.getLoadBalancingStrategy()) 65 | .useProtocol(properties.getProtocol()); 66 | properties.getHosts().stream().map(this::parseHost) 67 | .forEach(host -> builder.host(host[0], Integer.valueOf(host[1]))); 68 | return builder; 69 | } 70 | 71 | private String[] parseHost(final String host) { 72 | final String[] split = host.split(":"); 73 | if (split.length != 2 || !split[1].matches("[0-9]+")) { 74 | throw new ArangoDBException(String.format( 75 | "Could not load host '%s' from property-value spring.data.arangodb.hosts. Expected format ip:port,ip:port,...", 76 | host)); 77 | } 78 | return split; 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/main/java/com/arangodb/graphql/spring/boot/autoconfigure/ArangoGraphQLAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * DISCLAIMER 3 | * Copyright 2019 ArangoDB GmbH, Cologne, Germany 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Copyright holder is ArangoDB GmbH, Cologne, Germany 18 | * 19 | */ 20 | 21 | package com.arangodb.graphql.spring.boot.autoconfigure; 22 | 23 | import com.arangodb.ArangoDB; 24 | import com.arangodb.graphql.ArangoDataFetcher; 25 | import com.arangodb.graphql.create.DatabaseObjectCreator; 26 | import com.arangodb.graphql.schema.runtime.ArangoRuntimeWiringBuilder; 27 | import com.arangodb.graphql.schema.runtime.TypeDiscriminatorRegistry; 28 | import com.arangodb.graphql.generator.ArangoQueryGeneratorChain; 29 | import com.arangodb.graphql.generator.DefaultArangoQueryGeneratorChain; 30 | import com.arangodb.graphql.query.ArangoTraversalQueryExecutor; 31 | import com.arangodb.graphql.spring.ArangoGraphController; 32 | import graphql.GraphQL; 33 | import graphql.schema.*; 34 | import graphql.schema.idl.*; 35 | import org.springframework.beans.factory.annotation.Autowired; 36 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 37 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 38 | import org.springframework.context.annotation.Bean; 39 | import org.springframework.context.annotation.Configuration; 40 | import org.springframework.core.io.Resource; 41 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 42 | 43 | import java.io.IOException; 44 | import java.io.InputStreamReader; 45 | import java.util.*; 46 | 47 | /** 48 | * Auto Configuration for the GraphQL Components for ArangoDB 49 | * 50 | * @author Colin Findlay 51 | */ 52 | @Configuration 53 | @EnableConfigurationProperties(ArangoGraphQLConfigurationProperties.class) 54 | public class ArangoGraphQLAutoConfiguration { 55 | 56 | @Autowired 57 | private ArangoGraphQLConfigurationProperties properties; 58 | 59 | @Bean 60 | public ArangoDataFetcher arangoDataFetcher(ArangoQueryGeneratorChain queryGenerator, ArangoTraversalQueryExecutor queryExecutor){ 61 | return new ArangoDataFetcher(queryGenerator, queryExecutor); 62 | } 63 | 64 | @Bean 65 | public ArangoQueryGeneratorChain arangoQueryGeneratorChain(){ 66 | return new DefaultArangoQueryGeneratorChain(); 67 | } 68 | 69 | @Bean 70 | public ArangoTraversalQueryExecutor arangoTraversalQueryExecutor(ArangoDB.Builder builder){ 71 | return new ArangoTraversalQueryExecutor(builder.build(), properties.getDatabase()); 72 | } 73 | 74 | @Bean 75 | public ArangoGraphController arangoGraphController(GraphQL graphQL) throws IOException { 76 | return new ArangoGraphController(graphQL); 77 | } 78 | 79 | @Bean 80 | public TypeDefinitionRegistry typeDefinitionRegistry() throws IOException { 81 | 82 | SchemaParser schemaParser = new SchemaParser(); 83 | 84 | String schemaLocation = properties.getSchemaLocation(); 85 | if(schemaLocation == null){ 86 | schemaLocation = "*.graphqls"; 87 | } 88 | 89 | TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry(); 90 | 91 | PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 92 | Resource[] resources = resolver.getResources(schemaLocation); 93 | 94 | Set allResources = new LinkedHashSet<>(resources.length+1); 95 | // allResources.add(classPathResource); 96 | allResources.addAll(Arrays.asList(resources)); 97 | 98 | for(Resource resource : allResources){ 99 | typeRegistry.merge(schemaParser.parse(new InputStreamReader(resource.getInputStream()))); 100 | } 101 | 102 | return typeRegistry; 103 | 104 | } 105 | 106 | @Bean 107 | public TypeDiscriminatorRegistry typeDiscriminatorRegistry(TypeDefinitionRegistry typeDefinitionRegistry){ 108 | return new TypeDiscriminatorRegistry(typeDefinitionRegistry); 109 | } 110 | 111 | 112 | @Bean 113 | public GraphQLSchema graphQLSchema(ArangoDataFetcher fetcher, TypeDefinitionRegistry typeDefinitionRegistry, TypeDiscriminatorRegistry typeDiscriminatorRegistry){ 114 | 115 | RuntimeWiring runtimeWiring = ArangoRuntimeWiringBuilder.newArangoRuntimeWiring(fetcher, typeDefinitionRegistry, typeDiscriminatorRegistry); 116 | 117 | SchemaGenerator schemaGenerator = new SchemaGenerator(); 118 | return schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); 119 | 120 | } 121 | 122 | @Bean 123 | public GraphQL graphQL(GraphQLSchema graphQLSchema){ 124 | return GraphQL.newGraphQL(graphQLSchema).build(); 125 | } 126 | 127 | @Bean 128 | public DatabaseObjectCreator databaseObjectCreator(ArangoDB.Builder arango, GraphQLSchema graphQLSchema){ 129 | 130 | return new DatabaseObjectCreator(arango.build(), properties.getDatabase(), graphQLSchema); 131 | 132 | } 133 | 134 | @Bean 135 | @ConditionalOnProperty(name="arangodb.autoCreate", havingValue = "true") 136 | public AutomaticDatabaseObjectCreator automaticDatabaseObjectCreator(DatabaseObjectCreator databaseObjectCreator){ 137 | return new AutomaticDatabaseObjectCreator(databaseObjectCreator); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/arangodb/graphql/spring/boot/autoconfigure/ArangoGraphQLConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * DISCLAIMER 3 | * Copyright 2019 ArangoDB GmbH, Cologne, Germany 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Copyright holder is ArangoDB GmbH, Cologne, Germany 18 | * 19 | */ 20 | 21 | package com.arangodb.graphql.spring.boot.autoconfigure; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | 26 | import org.springframework.boot.context.properties.ConfigurationProperties; 27 | 28 | import com.arangodb.Protocol; 29 | import com.arangodb.entity.LoadBalancingStrategy; 30 | import com.arangodb.internal.ArangoDefaults; 31 | 32 | /** 33 | * 34 | * Initial implementation borrowed from the Arango spring-data Spring Boot Starter 35 | * 36 | * This version adds properties for GraphQL 37 | * 38 | * @author Mark Vollmary 39 | * @author Colin Findlay 40 | * 41 | */ 42 | @ConfigurationProperties(prefix = "arangodb") 43 | public class ArangoGraphQLConfigurationProperties { 44 | 45 | /** 46 | * GraphQL Schema Location 47 | */ 48 | private String schemaLocation; 49 | 50 | /** 51 | * Database name. 52 | */ 53 | private String database = "_system"; 54 | 55 | /** 56 | * Hosts to connect to. Multiple hosts can be added to provide fallbacks in a 57 | * single server with active failover or load balancing in an cluster setup. 58 | */ 59 | private Collection hosts = new ArrayList<>(); 60 | 61 | /** 62 | * Username to use for authentication. 63 | */ 64 | private String user = ArangoDefaults.DEFAULT_USER; 65 | 66 | /** 67 | * Password for the user for authentication. 68 | */ 69 | private String password; 70 | 71 | /** 72 | * Connection and request timeout in milliseconds. 73 | */ 74 | private Integer timeout = ArangoDefaults.DEFAULT_TIMEOUT; 75 | 76 | /** 77 | * If set to {@code true} SSL will be used when connecting to an ArangoDB 78 | * server. 79 | */ 80 | private Boolean useSsl = ArangoDefaults.DEFAULT_USE_SSL; 81 | 82 | /** 83 | * Maximum number of connections the built in connection pool will open per 84 | * host. 85 | */ 86 | private Integer maxConnections = ArangoDefaults.MAX_CONNECTIONS_VST_DEFAULT; 87 | 88 | /** 89 | * Maximum time to life of a connection. 90 | */ 91 | private Long connectionTtl; 92 | 93 | /** 94 | * Whether or not the driver should acquire a list of available coordinators in 95 | * an ArangoDB cluster or a single server with active failover. 96 | */ 97 | private Boolean acquireHostList = ArangoDefaults.DEFAULT_ACQUIRE_HOST_LIST; 98 | 99 | /** 100 | * Load balancing strategy to be used in an ArangoDB cluster setup. 101 | */ 102 | private LoadBalancingStrategy loadBalancingStrategy = ArangoDefaults.DEFAULT_LOAD_BALANCING_STRATEGY; 103 | 104 | /** 105 | * Network protocol to be used to connect to ArangoDB. 106 | */ 107 | private Protocol protocol = ArangoDefaults.DEFAULT_NETWORK_PROTOCOL; 108 | 109 | /** 110 | * Whether we should attempt to auto create ArangoDB databases, collections and indexes from the GraphQL schema 111 | */ 112 | private boolean autoCreate; 113 | 114 | public String getSchemaLocation() { 115 | return schemaLocation; 116 | } 117 | 118 | public void setSchemaLocation(String schemaLocation) { 119 | this.schemaLocation = schemaLocation; 120 | } 121 | 122 | public String getDatabase() { 123 | return database; 124 | } 125 | 126 | public void setDatabase(final String database) { 127 | this.database = database; 128 | } 129 | 130 | public String getUser() { 131 | return user; 132 | } 133 | 134 | public void setUser(final String user) { 135 | this.user = user; 136 | } 137 | 138 | public String getPassword() { 139 | return password; 140 | } 141 | 142 | public void setPassword(final String password) { 143 | this.password = password; 144 | } 145 | 146 | public Collection getHosts() { 147 | return hosts; 148 | } 149 | 150 | public void setHosts(final Collection hosts) { 151 | this.hosts = hosts; 152 | } 153 | 154 | public Integer getTimeout() { 155 | return timeout; 156 | } 157 | 158 | public void setTimeout(final Integer timeout) { 159 | this.timeout = timeout; 160 | } 161 | 162 | public Boolean getUseSsl() { 163 | return useSsl; 164 | } 165 | 166 | public void setUseSsl(final Boolean useSsl) { 167 | this.useSsl = useSsl; 168 | } 169 | 170 | public Integer getMaxConnections() { 171 | return maxConnections; 172 | } 173 | 174 | public void setMaxConnections(final Integer maxConnections) { 175 | this.maxConnections = maxConnections; 176 | } 177 | 178 | public Long getConnectionTtl() { 179 | return connectionTtl; 180 | } 181 | 182 | public void setConnectionTtl(final Long connectionTtl) { 183 | this.connectionTtl = connectionTtl; 184 | } 185 | 186 | public Boolean getAcquireHostList() { 187 | return acquireHostList; 188 | } 189 | 190 | public void setAcquireHostList(final Boolean acquireHostList) { 191 | this.acquireHostList = acquireHostList; 192 | } 193 | 194 | public LoadBalancingStrategy getLoadBalancingStrategy() { 195 | return loadBalancingStrategy; 196 | } 197 | 198 | public void setLoadBalancingStrategy(final LoadBalancingStrategy loadBalancingStrategy) { 199 | this.loadBalancingStrategy = loadBalancingStrategy; 200 | } 201 | 202 | public Protocol getProtocol() { 203 | return protocol; 204 | } 205 | 206 | public void setProtocol(final Protocol protocol) { 207 | this.protocol = protocol; 208 | } 209 | 210 | 211 | public boolean isAutoCreate() { 212 | return autoCreate; 213 | } 214 | 215 | public void setAutoCreate(boolean autoCreate) { 216 | this.autoCreate = autoCreate; 217 | } 218 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 25 | 4.0.0 26 | 27 | com.arangodb 28 | arangodb-graphql-spring-boot-starter 29 | 1.2 30 | 31 | arangodb-graphql-spring-boot-starter 32 | ArangoDB Graphql Spring Boot Starter 33 | https://github.com/ArangoDB-Community/arangodb-graphql-spring-boot-starter 34 | 35 | 36 | 37 | Apache License 2.0 38 | http://www.apache.org/licenses/LICENSE-2.0 39 | repo 40 | 41 | 42 | 43 | 44 | 45 | Colin Findlay 46 | 47 | 48 | Michele Rastelli 49 | https://github.com/rashtao 50 | 51 | 52 | 53 | 54 | UTF-8 55 | 2.1.7.RELEASE 56 | 1.8 57 | 1.8 58 | 1.8 59 | 60 | 61 | 62 | 63 | 64 | org.sonatype.plugins 65 | nexus-staging-maven-plugin 66 | 1.6.8 67 | true 68 | 69 | ossrh 70 | https://oss.sonatype.org/ 71 | 84aff6e87e214c 72 | false 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-resources-plugin 78 | 3.1.0 79 | 80 | UTF-8 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-source-plugin 86 | 3.1.0 87 | 88 | 89 | 90 | jar 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-javadoc-plugin 98 | 3.1.1 99 | 100 | 101 | attach-javadocs 102 | 103 | jar 104 | 105 | 106 | 107 | 108 | 109 | maven-deploy-plugin 110 | 2.8.2 111 | 112 | false 113 | 10 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-gpg-plugin 119 | 1.6 120 | 121 | 122 | sign-artifacts 123 | verify 124 | 125 | sign 126 | 127 | 128 | 129 | 130 | 131 | org.apache.maven.plugins 132 | maven-assembly-plugin 133 | 134 | 135 | assembly 136 | package 137 | 138 | single 139 | 140 | 141 | 142 | 143 | 144 | ${project.artifactId}-${project.version}-standalone 145 | 146 | false 147 | false 148 | 149 | jar-with-dependencies 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | org.springframework.boot 159 | spring-boot-starter 160 | ${spring-boot.version} 161 | provided 162 | 163 | 164 | 165 | com.arangodb 166 | arangodb-graphql-spring 167 | 1.2 168 | 169 | 170 | org.springframework.boot 171 | spring-boot-starter-test 172 | ${spring-boot.version} 173 | test 174 | 175 | 176 | org.springframework.boot 177 | spring-boot-starter-web 178 | ${spring-boot.version} 179 | test 180 | 181 | 182 | 183 | 184 | 185 | ossrh 186 | https://oss.sonatype.org/content/repositories/snapshots 187 | 188 | 189 | ossrh 190 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 191 | 192 | 193 | 194 | 195 | 196 | arangodb-snapshots 197 | https://oss.sonatype.org/content/groups/staging 198 | 199 | 200 | 201 | 202 | https://github.com/ArangoDB-Community/arangodb-graphql-java 203 | scm:git:git://github.com/ArangoDB-Community/arangodb-graphql-java.git 204 | scm:git:git://github.com/ArangoDB-Community/arangodb-graphql-java.git 205 | 206 | 207 | 208 | ArangoDB GmbH 209 | https://www.arangodb.com 210 | 211 | 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The ArangoDB GraphQL Spring Boot starter can be added to a Spring Boot application to add a GraphQL query interface to 4 | your application with ease and have it query your Arango Database. 5 | 6 | # Getting Started 7 | 8 | To get started, first add the starter to your project 9 | ```xml 10 | 11 | com.arangodb 12 | arangodb-graphql-spring-boot-starter 13 | 1.1 14 | 15 | ``` 16 | 17 | 18 | Create a new GraphQL schema called `schema.graphqls` in your `src/main/resources` directory. 19 | 20 | In your new GraphQL schema, declare the following special directives at the start of your file 21 | 22 | ```graphql schema 23 | directive @edge(collection : String!, direction: String!) on FIELD_DEFINITION 24 | directive @vertex(collection : String!) on OBJECT 25 | directive @discriminator(property : String) on INTERFACE | UNION 26 | directive @alias(name : String) on OBJECT 27 | directive @edgeTarget on FIELD_DEFINITION 28 | ``` 29 | 30 | These directives will be used to add metadata to your GraphQL schema to help map your GraphQL types to your underlying 31 | physical data model in ArangoDB. 32 | 33 | # Requirements 34 | 35 | Currently, Java 8 is required. We plan to support newer Java versions in the future. 36 | 37 | The current implementation uses the sync driver for ArangoDB. We also plan to support the 38 | async driver in the future. 39 | 40 | # Building and Testing 41 | 42 | ## Build the Libraries from scratch 43 | 44 | To get started you can build the root POM in this directory with 45 | 46 | `mvn install` 47 | 48 | ## Set up your ArangoDB database 49 | 50 | In ArangoDB - you can create any of the Example Graphs documented here: 51 | https://www.arangodb.com/docs/3.4/graphs.html#example-graphs 52 | 53 | ## Configure your ArangoDB Connection Details 54 | 55 | Edit the `/src/main/resources/application.yaml` file to 56 | have your connection details in the arangodb section, as shown [here](/src/test/resources/application.yaml). 57 | 58 | ## Run your example GraphQL Service 59 | 60 | In the [test resources](/src/test/resources), there is a Spring profile to match each of the example graphs. 61 | The profile names are: 62 | - city 63 | - knows 64 | - mps 65 | - social 66 | - traversal 67 | - world 68 | 69 | # Example 70 | 71 | Let's walk through a simple example with a simple Graph. Our Graph contains two entities Owners and Cars. Owners and 72 | Cars are linked by an edge indicating ownership. 73 | 74 | Owners --> Cars 75 | 76 | In Arango we have created this as an Owner document collection, a Car document collection and an ownership edge 77 | collection. 78 | 79 | In GraphQL schema, we represent the two document collections as types, with a @vertex directive to indicate what 80 | collection it lives in. 81 | 82 | ## Create a GraphQL Schema 83 | 84 | We can represent the properties of the document in each collection on each type, and we can represent the edge by 85 | declaring a property and adding the @edge directive to indicate where the edges live that represent that property. 86 | 87 | ```graphql schema 88 | type Owner @vertex(collection: "Owners") { 89 | _id: String! 90 | name: String! 91 | cars: [Car] @edge(collection: "ownership", direction: "outbound") 92 | } 93 | 94 | type Car @vertex(collection: "Cars") { 95 | _id: String! 96 | make: String! 97 | model: String! 98 | } 99 | ``` 100 | 101 | We can now create our Query operations in our schema. Suppose we as a query operation to look up an Owner by the name 102 | property of the document it would look like this. 103 | 104 | ```graphql schema 105 | type Query { 106 | 107 | findOwner(name: String): [Owner] 108 | 109 | } 110 | ``` 111 | 112 | ## Connecting to ArangoDB 113 | 114 | In order to connect to ArangoDB - you must provide the following configuration properties in your application.yaml file. 115 | 116 | ```yaml 117 | arangodb: 118 | hosts: : 119 | user: 120 | password: 121 | database: 122 | ``` 123 | 124 | You can also auto create your database, collections and indexes from your GraphQL schema by enabling the following 125 | property in the application.yaml file 126 | 127 | ```yaml 128 | arangodb: 129 | autoCreate: true 130 | ``` 131 | 132 | 133 | ## Adding graphiql 134 | 135 | To add GraphiQL to your project to give you a web UI to submit queries - simply add the following to you POM. 136 | 137 | ```XML 138 | 139 | com.graphql-java 140 | graphiql-spring-boot-starter 141 | 5.0.2 142 | 143 | ``` 144 | 145 | ## Running the example 146 | 147 | If we run this example we could submit the following GraphQL Query, to get owners names `Colin` and the makes and models 148 | of their cars. 149 | 150 | ```graphql 151 | query { 152 | findOwner(name: "Colin"){ 153 | name 154 | cars { 155 | make, 156 | model 157 | } 158 | } 159 | } 160 | ``` 161 | If you execute this query with a sample database you will see the following responses. 162 | 163 | ```json 164 | { 165 | "data": { 166 | "findOwner": [ 167 | { 168 | "name": "Colin", 169 | "cars": [ 170 | { 171 | "make": "BMW", 172 | "model": "3 SERIES" 173 | }, 174 | { 175 | "make": "MERCEDES-BENZ", 176 | "model": "GLE" 177 | } 178 | ] 179 | } 180 | ] 181 | } 182 | } 183 | ``` 184 | 185 | ## Bi-directional Traversal 186 | 187 | Let's expand on this example to navigate the graph in the opposite direction. Let suppose I want to find out who owns a 188 | particular make and model of car. To do this I would need to add another query operation to allow me to look up cars by 189 | Make and Model 190 | 191 | ```graphql schema 192 | type Query { 193 | 194 | findOwner(name: String): [Owner] 195 | findCar(make: String, model: String): [Car] 196 | 197 | } 198 | ``` 199 | 200 | I would also need to adjust my definition of the Car type so that I can navigate the edge in reverse. I do this by 201 | adding an owners property to the Car type and adding the @edge directive to indicate I should look for inbound edges 202 | from the ownership collection 203 | 204 | ```graphql schema 205 | type Car @vertex(collection: "Cars") { 206 | _id: String! 207 | make: String! 208 | model: String! 209 | owners: [Owner] @edge(collection: "ownership", direction: "inbound") 210 | } 211 | ``` 212 | 213 | Now we are able to query for say Owners of a BMW 3 Series 214 | 215 | ```graphql 216 | query { 217 | findCar(make: "BMW", model: "3 SERIES"){ 218 | make 219 | model 220 | owners { 221 | name 222 | } 223 | } 224 | } 225 | 226 | ``` 227 | Where the result would be as below. We can see that Robert & Colin both are owners of a BMW 3 Series. 228 | 229 | ```json 230 | { 231 | "data": { 232 | "findCar": [ 233 | { 234 | "make": "BMW", 235 | "model": "3 SERIES", 236 | "owners": [ 237 | { 238 | "name": "Robert" 239 | } 240 | ] 241 | }, 242 | { 243 | "make": "BMW", 244 | "model": "3 SERIES", 245 | "owners": [ 246 | { 247 | "name": "Colin" 248 | } 249 | ] 250 | } 251 | ] 252 | } 253 | } 254 | ``` 255 | 256 | As we now have a bi-directional relationship in our query we can now query both ways via GraphQL. 257 | 258 | If I want to see who owns a BMW 3 Series, and what other cars they have I can traverse from the Cars to the Owners and 259 | back to the Cars those Owner own. 260 | 261 | ```graphql 262 | query { 263 | findCar(make: "BMW", model: "3 SERIES"){ 264 | make 265 | model 266 | owners { 267 | name 268 | cars { 269 | make, 270 | model 271 | } 272 | } 273 | } 274 | } 275 | ``` 276 | 277 | Which give us the below result, where I can see Colin also has a Mercedes GLE. 278 | 279 | ```json 280 | { 281 | "data": { 282 | "findCar": [ 283 | { 284 | "make": "BMW", 285 | "model": "3 SERIES", 286 | "owners": [ 287 | { 288 | "name": "Colin", 289 | "cars": [ 290 | { 291 | "make": "BMW", 292 | "model": "3 SERIES" 293 | }, 294 | { 295 | "make": "MERCEDES-BENZ", 296 | "model": "GLE" 297 | } 298 | ] 299 | } 300 | ] 301 | }, 302 | { 303 | "make": "BMW", 304 | "model": "3 SERIES", 305 | "owners": [ 306 | { 307 | "name": "Robert", 308 | "cars": [ 309 | { 310 | "make": "BMW", 311 | "model": "3 SERIES" 312 | } 313 | ] 314 | } 315 | ] 316 | } 317 | ] 318 | } 319 | } 320 | ``` 321 | 322 | ## Filtering 323 | 324 | The ArangoDB GraphQL library allows you to apply filtering on any field in your schema by adding optional arguments to 325 | the field. 326 | 327 | To quote the GraphQL documentation: 328 | 329 | > If the only thing we could do was traverse objects and their fields, GraphQL would already be a very useful language 330 | for data fetching. But when you add the ability to pass arguments to fields, things get much more interesting. 331 | In a system like REST, you can only pass a single set of arguments - the query parameters and URL segments in your 332 | request. But in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete 333 | replacement for making multiple API fetches. 334 | 335 | This library will automatically convert the arguments to `FILTER` statements in an AQL query to allow for filtering to 336 | occur in the ArangoDB. The filters that you can apply are completely controlled by the content of the GraphQL Schema. 337 | 338 | Let's walk through an example. 339 | 340 | Suppose we want to filter the cars an owner has by fuel type. To do that we can add a fuel field to our Car type, to 341 | allow us to see the fuel attribute of car documents in the Cars collection. We then add a `fuel` argument to the cars 342 | property of the Owner type to allow us to filter the cars by fuel type. 343 | 344 | ```graphql schema 345 | type Owner @vertex(collection: "Owners") { 346 | _id: String! 347 | name: String! 348 | cars(fuel: String): [Car] @edge(collection: "ownership", direction: "outbound") 349 | } 350 | 351 | type Car @vertex(collection: "Cars") { 352 | _id: String! 353 | make: String! 354 | model: String! 355 | fuel: String! 356 | owners: [Owner] @edge(collection: "ownership", direction: "inbound") 357 | } 358 | ``` 359 | 360 | This will allow us to specify an argument on the cars property of an Owner type in our query 361 | 362 | ```graphql 363 | query { 364 | findOwner { 365 | name 366 | cars (fuel: "DIESEL") { 367 | make, 368 | model 369 | fuel 370 | } 371 | } 372 | } 373 | ``` 374 | 375 | Which would give us the following result as Colin is the only owner of diesel cars. 376 | 377 | ```json 378 | { 379 | "data": { 380 | "findOwner": [ 381 | { 382 | "name": "Colin", 383 | "cars": [ 384 | { 385 | "make": "BMW", 386 | "model": "3 SERIES", 387 | "fuel": "DIESEL" 388 | }, 389 | { 390 | "make": "MERCEDES-BENZ", 391 | "model": "GLE", 392 | "fuel": "DIESEL" 393 | } 394 | ] 395 | } 396 | ] 397 | } 398 | } 399 | ``` 400 | 401 | ### Multiple Filters 402 | 403 | If you add multiple arguments to the field these will each become an AQL `FILTER` statement and as such constitute a 404 | logical `AND`. For example adding make, model and fuel. 405 | 406 | ```graphql schema 407 | type Owner @vertex(collection: "Owners") { 408 | _id: String! 409 | name: String! 410 | cars(make: String, model: String, fuel: String): [Car] @edge(collection: "ownership", direction: "outbound") 411 | } 412 | ``` 413 | 414 | Would allow us to specify multiple arguments. In the example below, we specify a make and a fuel which would match cars 415 | that were Mercedes-Benz Diesels. You will also notice that although we could also specify a model, the argument is 416 | declared as optional in the schema - and as such is optional in the query. As it is not provided, we won't filter models. 417 | 418 | ```graphql 419 | query { 420 | findOwner { 421 | name 422 | cars (make: "MERCEDES-BENZ", fuel: "DIESEL") { 423 | make, 424 | model 425 | fuel 426 | } 427 | } 428 | } 429 | ``` 430 | 431 | ## Limit, Skip and Sort 432 | 433 | The library also supports Limit, Skip and Sort. If you declare arguments on your query operation named,"limit", "skip", 434 | or "sort" it will handle them accordingly. 435 | 436 | Limit and Skip have the same meaning as the AQL documentation for Limit and Skip, and should be declared as Int type. 437 | You can use these arguments for implementing pagination, or simply limiting the number of results returned. 438 | 439 | Sort can be declared as a custom input type, as long as all its properties are scalar and can represent the String 440 | "ASC" or "DESC". An enum is a good choice as shown in the below example. 441 | 442 | ```graphql schema 443 | enum SortDirection { 444 | ASC, 445 | DESC 446 | } 447 | 448 | input ClientSort { 449 | firstName: SortDirection 450 | lastName: SortDirection 451 | } 452 | 453 | type Query { 454 | getClients(limit: Int, skip: Int, sort: ClientSort): [Client] 455 | } 456 | ``` 457 | 458 | With the above schema you could have a query like this - get 10 clients, skip the first 50 ordered by ascending last 459 | name: 460 | 461 | ```graphql 462 | query { 463 | getClients(limit: 10, skip: 50, sort: { lastName: ASC }) { 464 | firstName 465 | lastName 466 | } 467 | } 468 | ``` 469 | 470 | ## Complex Edges 471 | 472 | You may wish to include information on an Edge document in ArangoDB, and have that be made available via your GraphQL 473 | interface 474 | 475 | In our example, lets assume that our edge documents in the ownership collection have a property called `finance` which 476 | may have a value of 477 | 478 | - HP (Hire Purchase) 479 | - PCP (Personal Contract Purchase) 480 | - PCH (Personal Contract Hire) 481 | 482 | In our example so far we have not exposed this property via our GraphQL interface. There are two ways to do this which 483 | are detailed below. 484 | 485 | ### Automatic Merge 486 | 487 | In this scenario the properties on the edge document are automatically merged with the target document. To do this we 488 | simply add the property that is on the edge document to the target type. In this example we add a `finance` property to 489 | the car type. 490 | 491 | ```graphql schema 492 | type Car @vertex(collection: "Cars") { 493 | _id: String! 494 | make: String! 495 | model: String! 496 | fuel: String! 497 | finance: String 498 | owners: [Owner] @edge(collection: "ownership", direction: "inbound") 499 | } 500 | ``` 501 | 502 | When we traverse from the Owner --> Car via the ownership edge, the `finance` property from the ownership 503 | edge will be automatically merged into the Car instance. In the case of a conflict where an edge and a target have the 504 | same property, the target value always takes precedence. 505 | 506 | The limitation of this approach is if we traverse to a Car not using the ownership edge or access it directly the 507 | property value will always be null. 508 | 509 | So for example if we use the `findCar` operation we defined earlier - this access the type directly, not via a traversal 510 | so the `finance` property will always be null. 511 | 512 | This approach is a good choice if 513 | 514 | - You only access the type via a single type of edge 515 | - You do not access the type directly 516 | - You only traverse the edge leading to this type in an outbound direction 517 | 518 | For example the following query will traverse from Owner --> Car via the Ownership edge and as a result the finance 519 | property will be populated from the edge. 520 | 521 | ```graphql 522 | query { 523 | findOwner { 524 | name 525 | cars { 526 | make, 527 | model 528 | fuel 529 | finance 530 | } 531 | } 532 | } 533 | ``` 534 | 535 | For example the following query will not traverse from Owner --> Car via the Ownership edge and as a result the finance 536 | property will be null. 537 | 538 | ```graphql 539 | query { 540 | findCar { 541 | make, 542 | model, 543 | finance 544 | } 545 | } 546 | ``` 547 | 548 | In this scenario, automatic merging is a suboptimal solution because our Car type specifies a property that is not 549 | consistently populated. 550 | 551 | ### Edge Target 552 | 553 | In this scenario, you create an intermediate type in your GraphQL schema to represent the edge relationship. 554 | 555 | Here we create an Ownership type, with the `finance` property from the edge document, and a special property for the 556 | target of the edge marked with the `@edgeTarget` directive to indicate the target of the edge should be placed here. 557 | 558 | ```graphql schema 559 | type Ownership { 560 | finance: String 561 | car: Car @edgeTarget 562 | } 563 | ``` 564 | 565 | We then need to adjust our `Owner` type to make the cars field return the `Ownership` intermediate type we just created. 566 | 567 | ```graphql schema 568 | type Owner @vertex(collection: "Owners") { 569 | _id: String! 570 | name: String! 571 | cars(make: String, model: String, fuel: String): [Ownership] @edge(collection: "ownership", direction: "outbound") 572 | } 573 | ``` 574 | 575 | This has an impact on the GraphQL query - notice how the finance property is now a sibling to the car, rather than a 576 | child of it. This now means we can now access the Car type directly without it being polluted by properties that can 577 | only be populated when the type is accessed in a certain way. 578 | 579 | ```graphql 580 | query { 581 | findOwner { 582 | name 583 | cars { 584 | finance 585 | car { 586 | make 587 | model 588 | } 589 | } 590 | } 591 | } 592 | ``` 593 | 594 | This mechanism also supports bi-directional traversal, however because the target of the edge is different when you 595 | traverse in the opposite direction, you need a second intermediate type to represent the edge in the reverse direction. 596 | Here we have created the `OwnedBy` type which has an `@edgeTarget` of type `Owner` 597 | 598 | ```graphql schema 599 | type OwnedBy { 600 | finance: String 601 | owner: Owner @edgeTarget 602 | } 603 | ``` 604 | 605 | We then need to adjust our `Car` type to make the owners field return the `OwnedBy` intermediate type we just created. 606 | 607 | ```graphql schema 608 | query { 609 | findCar { 610 | make 611 | model 612 | owners { 613 | finance 614 | owner{ 615 | name 616 | } 617 | } 618 | } 619 | } 620 | ``` 621 | 622 | ## Type Discrimination 623 | 624 | To help GraphQL detect the object type, you can add optional type discriminator metadata to your schema definitions. 625 | 626 | In order to deal with inheritance via interface and union types in GraphQL the Arango GraphQL Adapter implements two 627 | mechanisms to achieve type discrimination. 628 | 629 | ### Collection Based Type Discrimination 630 | 631 | The default option is to use Collection Based Type Discrimination. This makes an assumption that every Document 632 | collection you have in ArangoDB maps to one and only one type. 633 | 634 | In this first example using interfaces we see we have two collections 635 | 636 | - StandardCars 637 | - ConvertibleCars 638 | 639 | Instances of `StandardCar` are in the `StandardCars` collection, instances of `ConvertibleCar` are in the `ConvertibleCars` collection. 640 | 641 | ```graphql schema 642 | interface Car { 643 | make: String! 644 | model: String! 645 | variant: String! 646 | } 647 | 648 | type StandardCar implements Car @vertex(collection: "StandardCars") { 649 | make: String! 650 | model: String! 651 | variant: String! 652 | } 653 | 654 | type ConvertibleCar implements Car @vertex(collection: "ConvertibleCars") { 655 | make: String! 656 | model: String! 657 | variant: String! 658 | roofType: String! 659 | } 660 | ``` 661 | 662 | Alternatively you can achieve the same result with a union. 663 | 664 | ```graphql schema 665 | type StandardCar implements Car @vertex(collection: "StandardCars") { 666 | make: String! 667 | model: String! 668 | variant: String! 669 | } 670 | 671 | type ConvertibleCar implements Car @vertex(collection: "ConvertibleCars") { 672 | make: String! 673 | model: String! 674 | variant: String! 675 | roofType: String! 676 | } 677 | 678 | union Car = StandardCar | ConvertibleCar 679 | ``` 680 | 681 | This is the default option because it requires no additional configuration to achieve, but it is not intended to 682 | influence your design choices for how you structure the data in your Arango database. For more control, and the ability 683 | to store multiple document types in the same collection you will need to use Property Based Type Discrimination. 684 | 685 | 686 | ### Property Based Type Discrimination 687 | 688 | With Property Based Type Discrimination we use a property on a document to determine what concrete type to use. 689 | 690 | In the following example we have 691 | 692 | - A `Car` interface and 693 | - A `StandardCar` concrete type that implement the `Car` interface 694 | - A `ConvertibleCar` concrete type that implement the `Car` interface 695 | 696 | In order to use a property called `vehicleType` on instances of Vehicle to determine if they are a `StandardCar` or a `ConvertibleCar` 697 | we add the `@discriminator` directive to the `Vehicle` interface declaration. 698 | 699 | ```graphql schema 700 | interface Car @vertex(collection: "Cars") @discriminator(property: "vehicleType") { 701 | make: String! 702 | model: String! 703 | variant: String! 704 | } 705 | 706 | type StandardCar implements Car @vertex(collection: "Cars") { 707 | make: String! 708 | model: String! 709 | variant: String! 710 | } 711 | 712 | type ConvertibleCar implements Car @vertex(collection: "Cars") { 713 | make: String! 714 | model: String! 715 | variant: String! 716 | roofType: String! 717 | } 718 | ``` 719 | 720 | Or as a union 721 | 722 | ```graphql schema 723 | type StandardCar implements Car @vertex(collection: "Cars") { 724 | make: String! 725 | model: String! 726 | variant: String! 727 | } 728 | 729 | type ConvertibleCar implements Car @vertex(collection: "Cars") { 730 | make: String! 731 | model: String! 732 | variant: String! 733 | roofType: String! 734 | } 735 | 736 | union Vehicle = StandardCar | ConvertibleCar 737 | ``` 738 | 739 | Now the following document would be typed as a StandardCar 740 | 741 | ```json 742 | { 743 | "make": "BMW", 744 | "model": "1 SERIES", 745 | "variant": "M140i", 746 | "fuel": "PETROL", 747 | "vehicleType": "StandardCar" 748 | } 749 | ``` 750 | 751 | And the following document would be classed as a ConvertibleCar 752 | 753 | ```json 754 | { 755 | "make": "BMW", 756 | "model": "Z4", 757 | "variant": "M40i", 758 | "fuel": "PETROL", 759 | "vehicleType": "ConvertibleCar" 760 | } 761 | ``` 762 | 763 | ### Type Alias 764 | 765 | You will notice on the above example that the values in the vehicleType property in the documents directly match the 766 | names of the types in your GraphQL schema. This however might not always be practical, and you may want to map 767 | different values in the document on to types in the schema. 768 | 769 | For example - lets say that your document contained a fully qualified Java class name as they value you might have 770 | documents that look like this: 771 | 772 | ```json 773 | { 774 | "make": "BMW", 775 | "model": "1 SERIES", 776 | "variant": "M140i", 777 | "fuel": "PETROL", 778 | "vehicleType": "com.example.model.StandardCar" 779 | } 780 | ``` 781 | 782 | ```json 783 | { 784 | "make": "BMW", 785 | "model": "Z4", 786 | "variant": "M40i", 787 | "fuel": "PETROL", 788 | "vehicleType": "com.example.model.ConvertibleCar" 789 | } 790 | ``` 791 | 792 | In order for the Java type name in the document to map to our schema, you will need to create a type alias in your 793 | schema using the `@alias` directive. 794 | 795 | ```graphql schema 796 | interface Car @vertex(collection: "Cars") @discriminator(property: "vehicleType") { 797 | make: String! 798 | model: String! 799 | variant: String! 800 | } 801 | 802 | type StandardCar implements Car @vertex(collection: "Cars") @alias(name: "com.example.model.StandardCar"){ 803 | make: String! 804 | model: String! 805 | variant: String! 806 | } 807 | 808 | type ConvertibleCar implements Car @vertex(collection: "Cars") @alias(name: "com.example.model.ConvertibleCar"){ 809 | make: String! 810 | model: String! 811 | variant: String! 812 | roofType: String! 813 | } 814 | ``` 815 | 816 | 817 | ## Test 818 | 819 | To run the tests a running instance of ArangoDB is required, which can be started using docker: 820 | ```shell script 821 | docker run -p 8529:8529 -d -e ARANGO_NO_AUTH=1 --name arangodb arangodb/arangodb:3.5.0 822 | ``` 823 | and the graph examples should be loaded. For example for the `city` profile, the following is required: 824 | ```shell script 825 | docker exec arangodb arangosh --server.authentication=false --javascript.execute-string='require("@arangodb/graph-examples/example-graph.js").loadGraph("routeplanner")' 826 | ``` 827 | --------------------------------------------------------------------------------