This is an automated email from the ASF dual-hosted git repository. jbonofre pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/karaf.git
The following commit(s) were added to refs/heads/main by this push: new 431b3920c9 [KARAF-7567] Add GraphQL example new c1b4e670d7 This closes #1631 431b3920c9 is described below commit 431b3920c9281526997070695802a716c517bac8 Author: Aleksy Wróblewski <aleksy.wroblew...@bbbit.io> AuthorDate: Wed Oct 5 22:54:37 2022 +0200 [KARAF-7567] Add GraphQL example --- examples/karaf-graphql-example/README.md | 98 ++++++++++++++ .../karaf-graphql-example-api/pom.xml | 43 ++++++ .../apache/karaf/examples/graphql/api/Book.java | 54 ++++++++ .../karaf/examples/graphql/api/BookRepository.java | 28 ++++ .../graphql/api/GraphQLSchemaProvider.java | 24 ++++ .../karaf-graphql-example-commands/pom.xml | 78 +++++++++++ .../karaf/examples/graphql/commands/Query.java | 56 ++++++++ .../karaf-graphql-example-core/pom.xml | 59 +++++++++ .../examples/graphql/core/BookSchemaProvider.java | 103 +++++++++++++++ .../graphql/core/InMemoryBookRepository.java | 63 +++++++++ .../src/main/resources/schema.graphql | 41 ++++++ .../karaf-graphql-example-features/pom.xml | 79 +++++++++++ .../src/main/feature/feature.xml | 53 ++++++++ .../karaf-graphql-example-scr-servlet/pom.xml | 75 +++++++++++ .../graphql/servlet/ExampleGraphQLHttpServlet.java | 41 ++++++ .../karaf-graphql-example-websocket/pom.xml | 73 ++++++++++ .../graphql/websocket/GraphQLWebSocketExample.java | 92 +++++++++++++ .../graphql/websocket/GraphQLWebSocketServlet.java | 49 +++++++ examples/karaf-graphql-example/pom.xml | 44 ++++++ examples/pom.xml | 1 + .../karaf/itests/examples/GraphQLExampleTest.java | 147 +++++++++++++++++++++ .../itests/examples/WebSocketExampleTest.java | 50 +------ .../org/apache/karaf/itests/util/SimpleSocket.java | 66 +++++++++ 23 files changed, 1368 insertions(+), 49 deletions(-) diff --git a/examples/karaf-graphql-example/README.md b/examples/karaf-graphql-example/README.md new file mode 100644 index 0000000000..0019b924ae --- /dev/null +++ b/examples/karaf-graphql-example/README.md @@ -0,0 +1,98 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +# Apache Karaf GraphQL example + +## Abstract + +This example shows how to use GraphQL in Karaf. +We demonstrate how to use GraphQL with WebSockets, with a HTTP servlet and with Karaf commands. + +## Build +The build uses Apache Maven. Simply use: + +``` +mvn clean install +``` + +## Deployment + +On a running Karaf instance, add a feature repository and then the feature: +``` +karaf@root()> feature:repo-add mvn:org.apache.karaf.examples/karaf-graphql-example-features/LATEST/xml +karaf@root()> feature:install karaf-graphql-example +``` + +## Usage +A HTTP server will start on the configured port in Karaf (8181 by default). +The following endpoints can be used to test the GraphQL API: +``` +GET http://localhost:8181/graphql?query={ bookById(id:"1") { name }} +GET http://localhost:8181/graphql?query={ bookById(id:"2") { name id authorId pageCount}} + +POST http://localhost:8181/graphql +{ + "query": "mutation { addBook(name:\"Test\", pageCount:100) { name } }" +} +``` + +Additionally, a `graphql:query` command will be available. It takes a single mandatory argument +which needs to be a valid GraphQL query in the defined GraphQL schema. +For instance: +``` +karaf@root()> graphql:query "{books { name id pageCount }}" +{books=[{name=Apache Karaf Cookbook, id=1, pageCount=260}, {name=Effective Java, id=2, pageCount=416}, {name=OSGi in Action, id=3, pageCount=375}]} + +karaf@root()> graphql:query "{bookById(id:1) { name id pageCount }}" +{bookById={name=Apache Karaf Cookbook, id=1, pageCount=260}} + +karaf@root()> graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name }}" +{addBook={id=9, name=Lord of the Rings}} +``` + +Finally, the `karaf-graphql-example-websocket` bundle contains a WebSocket endpoint that will publish updates +when new data is added via GraphQL. To test, execute the following cURL command: +``` +curl --include \ + --no-buffer \ + --header "Connection: Upgrade" \ + --header "Upgrade: websocket" \ + --header "Host: localhost:8181" \ + --header "Origin: http://localhost:8181/graphql-websocket" \ + --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \ + --header "Sec-WebSocket-Version: 13" \ + http://localhost:8181/graphql-websocket +``` +You should see a similar response: +``` +HTTP/1.1 101 Switching Protocols +Date: Tue, 04 Oct 2022 21:07:55 GMT +Connection: Upgrade +Sec-WebSocket-Accept: qGEgH3En71di5rrssAZTmtRTyFk= +Upgrade: WebSocket + +``` + +Add a new book by with the GraphQL mutation (either with a POST request or Karaf command): +``` +karaf@root()> graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:123) { id name }}" +``` +and observe the update come in real time to the terminal with cURL: +``` +{bookCreated={id=6, name=Lord of the Rings}} +``` \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml new file mode 100644 index 0000000000..10435c2c0c --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-api/pom.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example</artifactId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-api</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: API</name> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>com.graphql-java</groupId> + <artifactId>graphql-java</artifactId> + <version>19.2</version> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java new file mode 100644 index 0000000000..141f7d69df --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/Book.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.api; + +public class Book { + + private final String name; + private final int pageCount; + private String id; + + public Book(String name, int pageCount) { + this.name = name; + this.pageCount = pageCount; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public int getPageCount() { + return pageCount; + } + + @Override + public String toString() { + return "Book{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", pageCount=" + pageCount + + '}'; + } +} \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java new file mode 100644 index 0000000000..d73355d0fa --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/BookRepository.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.api; + +import java.util.Collection; + +public interface BookRepository { + + Book storeBook(Book book); + + Collection<Book> getBooks(); + + Book getBookById(String id); +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java new file mode 100644 index 0000000000..8978ec5f3f --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-api/src/main/java/org/apache/karaf/examples/graphql/api/GraphQLSchemaProvider.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.api; + +import graphql.schema.GraphQLSchema; + +public interface GraphQLSchemaProvider { + + GraphQLSchema createSchema(); +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml new file mode 100644 index 0000000000..be58b084ae --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-commands/pom.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example</artifactId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-commands</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: Shell Commands</name> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.apache.karaf.shell</groupId> + <artifactId>org.apache.karaf.shell.core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.graphql-java</groupId> + <artifactId>graphql-java</artifactId> + <version>19.2</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <version>1.4.0</version> + </dependency> + <dependency> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.annotation</artifactId> + <version>8.0.0</version> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Karaf-Commands>*</Karaf-Commands> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java b/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java new file mode 100644 index 0000000000..683409874f --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-commands/src/main/java/org/apache/karaf/examples/graphql/commands/Query.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.commands; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.schema.GraphQLSchema; +import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider; +import org.apache.karaf.shell.api.action.Action; +import org.apache.karaf.shell.api.action.Argument; +import org.apache.karaf.shell.api.action.Command; +import org.apache.karaf.shell.api.action.lifecycle.Reference; +import org.apache.karaf.shell.api.action.lifecycle.Service; + +/** + * Sample calls: + * graphql:query "{books { name }}" + * graphql:query "{bookById(id:\"1\") { name id pageCount }}" + * graphql:query "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name }} + */ +@Service +@Command(scope = "graphql", name = "query", description = "Execute GraphQL query") +public class Query implements Action { + + @Argument(index = 0, name = "query", required = true, multiValued = false) + String query; + + @Reference + private GraphQLSchemaProvider schemaProvider; + + @Override + public Object execute() throws Exception { + GraphQLSchema schema = schemaProvider.createSchema(); + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(query); + if (result.getData() != null) { + System.out.println(result.getData().toString()); + } else { + result.getErrors().forEach(System.out::println); + } + return null; + } +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml new file mode 100644 index 0000000000..7d10181711 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-core/pom.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>karaf-graphql-example</artifactId> + <groupId>org.apache.karaf.examples</groupId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-core</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: Core</name> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <version>1.4.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.graphql-java</groupId> + <artifactId>graphql-java</artifactId> + <version>19.2</version> + </dependency> + <dependency> + <groupId>io.reactivex.rxjava3</groupId> + <artifactId>rxjava</artifactId> + <version>3.1.5</version> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java new file mode 100644 index 0000000000..8050ac198e --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/BookSchemaProvider.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.core; + +import graphql.schema.DataFetcher; +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.observables.ConnectableObservable; +import io.reactivex.rxjava3.subjects.PublishSubject; +import org.apache.karaf.examples.graphql.api.Book; +import org.apache.karaf.examples.graphql.api.BookRepository; +import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.reactivestreams.Publisher; + +import java.util.Collection; + +@Component(service = GraphQLSchemaProvider.class) +public class BookSchemaProvider implements GraphQLSchemaProvider { + + @Reference(service = BookRepository.class) + private BookRepository bookRepository; + + private PublishSubject<Book> subject; + + public void setBookRepository(InMemoryBookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + @Activate + public void activate() { + subject = PublishSubject.create(); + } + + @Override + public GraphQLSchema createSchema() { + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse( + this.getClass().getResourceAsStream("/schema.graphql")); + + RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() + .type("Mutation", builder -> builder.dataFetcher("addBook", addBookFetcher())) + .type("Query", builder -> builder.dataFetcher("bookById", bookByIdFetcher())) + .type("Query", builder -> builder.dataFetcher("books", booksFetcher())) + .type("Subscription", builder -> builder.dataFetcher("bookCreated", bookCreatedFetcher())) + .build(); + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + return schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + } + + private DataFetcher<Book> addBookFetcher() { + return environment -> { + String name = environment.getArgument("name"); + int pageCount = environment.getArgument("pageCount"); + Book book = bookRepository.storeBook(new Book(name, pageCount)); + subject.onNext(book); + return book; + }; + } + + private DataFetcher<Book> bookByIdFetcher() { + return environment -> { + String id = environment.getArgument("id"); + return bookRepository.getBookById(id); + }; + } + + private DataFetcher<Collection<Book>> booksFetcher() { + return environment -> bookRepository.getBooks(); + } + + private DataFetcher<Publisher<Book>> bookCreatedFetcher() { + return environment -> getPublisher(); + } + + private Publisher<Book> getPublisher() { + subject = PublishSubject.create(); + ConnectableObservable<Book> connectableObservable = subject.share().publish(); + connectableObservable.connect(); + return connectableObservable.toFlowable(BackpressureStrategy.BUFFER); + } +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java new file mode 100644 index 0000000000..b35d7b127a --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/java/org/apache/karaf/examples/graphql/core/InMemoryBookRepository.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.core; + +import org.apache.karaf.examples.graphql.api.Book; +import org.apache.karaf.examples.graphql.api.BookRepository; +import org.osgi.service.component.annotations.Component; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +@Component(service = BookRepository.class) +public class InMemoryBookRepository implements BookRepository { + + private final AtomicInteger idCounter = new AtomicInteger(0); + private final Map<String, Book> booksById = new HashMap<>(); + + public void activate() { + createInitialBooks(); + } + + @Override + public Book storeBook(Book book) { + String id = nextId(); + book.setId(id); + booksById.put(id, book); + return book; + } + + public Collection<Book> getBooks() { + return booksById.values(); + } + + public Book getBookById(String id) { + return booksById.get(id); + } + + private void createInitialBooks() { + storeBook(new Book("Apache Karaf Cookbook", 260)); + storeBook(new Book("Effective Java", 416)); + storeBook(new Book("OSGi in Action", 375)); + } + + private String nextId() { + return Integer.toString(idCounter.incrementAndGet()); + } +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql new file mode 100644 index 0000000000..d70b6c7c46 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-core/src/main/resources/schema.graphql @@ -0,0 +1,41 @@ +""" +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +schema { + query: Query + mutation: Mutation + subscription: Subscription +} + +type Query { + bookById(id: ID): Book + books: [Book] +} + +type Book { + id: ID + name: String + pageCount: Int +} + +type Subscription { + bookCreated: Book +} + +type Mutation { + addBook(name: String, pageCount: Int): Book +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml new file mode 100644 index 0000000000..e25741b9ec --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-features/pom.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>karaf-graphql-example</artifactId> + <groupId>org.apache.karaf.examples</groupId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-features</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: Features</name> + <packaging>pom</packaging> + + <build> + <resources> + <resource> + <directory>src/main/feature</directory> + <filtering>true</filtering> + <targetPath>${project.build.directory}/feature</targetPath> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>resources</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>attach-artifacts</id> + <phase>package</phase> + <goals> + <goal>attach-artifact</goal> + </goals> + <configuration> + <artifacts> + <artifact> + <file>target/feature/feature.xml</file> + <type>xml</type> + </artifact> + </artifacts> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml b/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml new file mode 100644 index 0000000000..fa70ccd9fd --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-features/src/main/feature/feature.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<features name="karaf-graphql-example" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.4.0"> + + <feature name="karaf-graphql-example" version="${project.version}"> + <feature prerequisite="true">scr</feature> + <feature prerequisite="true">http-whiteboard</feature> + <feature prerequisite="true">http</feature> + <feature prerequisite="true">wrap</feature> + <feature prerequisite="true">pax-web-karaf</feature> + <feature prerequisite="true">pax-web-jetty-websockets</feature> + + <bundle dependency="true">wrap:mvn:com.graphql-java/java-dataloader/3.2.0</bundle> + <bundle dependency="true">mvn:org.reactivestreams/reactive-streams/1.0.4</bundle> + <bundle dependency="true">wrap:mvn:com.graphql-java/graphql-java/19.2</bundle> + + <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version}</bundle> + <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version}</bundle> + <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version}</bundle> + <bundle dependency="true">mvn:com.fasterxml.jackson.datatype/jackson-datatype-jdk8/${jackson.version}</bundle> + <bundle dependency="true">mvn:javax.servlet/javax.servlet-api/4.0.1</bundle> + <bundle dependency="true">mvn:javax.websocket/javax.websocket-api/1.1</bundle> + + <bundle dependency="true">mvn:com.graphql-java-kickstart/graphql-java-kickstart/14.0.0</bundle> + <bundle dependency="true">mvn:com.graphql-java-kickstart/graphql-java-servlet/14.0.0</bundle> + + <bundle dependency="true">mvn:io.reactivex.rxjava3/rxjava/3.1.5</bundle> + <bundle dependency="true">mvn:org.eclipse.jetty.websocket/websocket-server/9.4.48.v20220622</bundle> + + <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-api/${project.version}</bundle> + <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-core/${project.version}</bundle> + <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-scr-servlet/${project.version}</bundle> + <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-commands/${project.version}</bundle> + <bundle>mvn:org.apache.karaf.examples/karaf-graphql-example-websocket/${project.version}</bundle> + </feature> + +</features> diff --git a/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml new file mode 100644 index 0000000000..5499b8a6fd --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/pom.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>karaf-graphql-example</artifactId> + <groupId>org.apache.karaf.examples</groupId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-scr-servlet</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: SCR Servlet</name> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <version>1.4.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.graphql-java</groupId> + <artifactId>graphql-java</artifactId> + <version>19.2</version> + </dependency> + <dependency> + <groupId>com.graphql-java-kickstart</groupId> + <artifactId>graphql-java-servlet</artifactId> + <version>14.0.0</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>4.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example-api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java new file mode 100644 index 0000000000..c53ab828d5 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-scr-servlet/src/main/java/org/apache/karaf/examples/graphql/servlet/ExampleGraphQLHttpServlet.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.examples.graphql.servlet; + +import graphql.kickstart.servlet.GraphQLConfiguration; +import graphql.kickstart.servlet.GraphQLHttpServlet; +import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import javax.servlet.Servlet; + +@Component(service = Servlet.class, property = {"alias=/graphql", "servlet-name=GraphQL"}) +public class ExampleGraphQLHttpServlet extends GraphQLHttpServlet { + + @Reference + private GraphQLSchemaProvider schemaProvider; + + public void setSchemaProvider(GraphQLSchemaProvider schemaProvider) { + this.schemaProvider = schemaProvider; + } + + @Override + protected GraphQLConfiguration getConfiguration() { + return GraphQLConfiguration.with(schemaProvider.createSchema()).build(); + } +} diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml b/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml new file mode 100644 index 0000000000..ddbddc0a42 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/pom.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>karaf-graphql-example</artifactId> + <groupId>org.apache.karaf.examples</groupId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example-websocket</artifactId> + <name>Apache Karaf :: Examples :: GraphQL :: WebSocket</name> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.eclipse.jetty.websocket</groupId> + <artifactId>websocket-servlet</artifactId> + <version>9.4.48.v20220622</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>karaf-graphql-example-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <version>1.4.0</version> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>11</source> + <target>11</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java new file mode 100644 index 0000000000..ce2fe6eea1 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketExample.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.karaf.examples.graphql.websocket; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +@WebSocket +public class GraphQLWebSocketExample { + + private static final List<Session> sessions = new ArrayList<>(); + private final GraphQL graphQL; + + public GraphQLWebSocketExample(GraphQL graphQL) { + this.graphQL = graphQL; + } + + @OnWebSocketConnect + public void onOpen(Session session) { + session.setIdleTimeout(-1); + sessions.add(session); + + String query = "" + + " subscription BookFeed {\n" + + " bookCreated {\n" + + " id\n" + + " name\n" + + " }\n" + + " }\n"; + + ExecutionResult executionResult = graphQL.execute(query); + Publisher<ExecutionResult> bookStream = executionResult.getData(); + AtomicReference<Subscription> subscriptionRef = new AtomicReference<>(); + bookStream.subscribe(new Subscriber<>() { + @Override + public void onSubscribe(Subscription subscription) { + subscriptionRef.set(subscription); + subscription.request(1); + } + + @Override + public void onNext(ExecutionResult executionResult) { + try { + session.getRemote().sendString(executionResult.getData().toString()); + subscriptionRef.get().request(1); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onError(Throwable throwable) { + } + + @Override + public void onComplete() { + } + }); + } + + @OnWebSocketClose + public void onClose(Session session, int statusCode, String reason) { + sessions.remove(session); + } +} \ No newline at end of file diff --git a/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java new file mode 100644 index 0000000000..ff126b3bc2 --- /dev/null +++ b/examples/karaf-graphql-example/karaf-graphql-example-websocket/src/main/java/org/apache/karaf/examples/graphql/websocket/GraphQLWebSocketServlet.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.karaf.examples.graphql.websocket; + +import graphql.GraphQL; +import graphql.execution.SubscriptionExecutionStrategy; +import org.apache.karaf.examples.graphql.api.GraphQLSchemaProvider; +import org.eclipse.jetty.websocket.servlet.*; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import javax.servlet.Servlet; +import javax.servlet.annotation.WebServlet; + +@WebServlet(name = "Example GraphQL WebSocket Servlet", urlPatterns = {"/graphql-websocket"}) +@Component(service = Servlet.class, property = {"osgi.http.whiteboard.servlet.pattern=/graphql-websocket"}) +public class GraphQLWebSocketServlet extends WebSocketServlet implements WebSocketCreator { + + @Reference(service = GraphQLSchemaProvider.class) + private GraphQLSchemaProvider schemaProvider; + + @Override + public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) { + GraphQL graphQL = GraphQL.newGraphQL(schemaProvider.createSchema()) + .subscriptionExecutionStrategy(new SubscriptionExecutionStrategy()) + .build(); + return new GraphQLWebSocketExample(graphQL); + } + + @Override + public void configure(WebSocketServletFactory factory) { + factory.setCreator(this); + } +} diff --git a/examples/karaf-graphql-example/pom.xml b/examples/karaf-graphql-example/pom.xml new file mode 100644 index 0000000000..e0348dfd45 --- /dev/null +++ b/examples/karaf-graphql-example/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.karaf.examples</groupId> + <artifactId>apache-karaf-examples</artifactId> + <version>4.4.2-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>karaf-graphql-example</artifactId> + <name>Apache Karaf :: Examples :: GraphQL Example</name> + <packaging>pom</packaging> + + <modules> + <module>karaf-graphql-example-scr-servlet</module> + <module>karaf-graphql-example-commands</module> + <module>karaf-graphql-example-core</module> + <module>karaf-graphql-example-api</module> + <module>karaf-graphql-example-websocket</module> + <module>karaf-graphql-example-features</module> + </modules> + +</project> diff --git a/examples/pom.xml b/examples/pom.xml index f683278fa3..73c183a67f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -67,6 +67,7 @@ <module>karaf-soap-example</module> <module>karaf-websocket-example</module> <module>karaf-war-example</module> + <module>karaf-graphql-example</module> </modules> </project> diff --git a/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java b/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java new file mode 100644 index 0000000000..d41d994cc4 --- /dev/null +++ b/itests/test/src/test/java/org/apache/karaf/itests/examples/GraphQLExampleTest.java @@ -0,0 +1,147 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.itests.examples; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.karaf.itests.BaseTest; +import org.apache.karaf.itests.util.SimpleSocket; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerMethod; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import static junit.framework.TestCase.assertTrue; + +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerMethod.class) +public class GraphQLExampleTest extends BaseTest { + + private void setUp() throws Exception { + addFeaturesRepository("mvn:org.apache.karaf.examples/karaf-graphql-example-features/" + System.getProperty("karaf.version") + "/xml"); + installAndAssertFeature("karaf-graphql-example"); + } + + @Test + public void testServlet() throws Exception { + setUp(); + + String getBooksQuery = "{ books { name id } }"; + String booksRequestResult = sendGetRequest(getBooksQuery); + + assertContains("\"name\":\"Apache Karaf Cookbook\"", booksRequestResult); + assertContains("\"id\":\"1\"", booksRequestResult); + assertContains("\"name\":\"Effective Java\"", booksRequestResult); + assertContains("\"id\":\"2\"", booksRequestResult); + + String addBookQuery = "mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name } }"; + String addBookRequestResult = sendPostRequest(addBookQuery); + assertContains("id", addBookRequestResult); + assertContains("Lord of the Rings", addBookRequestResult); + + String getBooksByIdQuery = "{ bookById(id:4) { name } }"; + String getBookByIdRequestResult = sendGetRequest(getBooksByIdQuery); + assertContains("Lord of the Rings", getBookByIdRequestResult); + } + + @Test + public void testCommand() throws Exception { + setUp(); + String getBooksQuery = "\"{ books { name id } }\""; + String output = executeCommand("graphql:query " + getBooksQuery); + System.out.println(output); + } + + @Test + public void testWebSocket() throws Exception { + setUp(); + + WebSocketClient client = new WebSocketClient(); + SimpleSocket socket = new SimpleSocket(); + client.start(); + URI uri = new URI("ws://localhost:" + getHttpPort() + "/graphql-websocket"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + client.connect(socket, uri, request); + + sendPostRequest("mutation { addBook(name:\"Lord of the Rings\" pageCount:100) { id name } }"); + + socket.awaitClose(10, TimeUnit.SECONDS); + + assertTrue(socket.messages.size() > 0); + + assertContains("Lord of the Rings", socket.messages.get(0)); + + client.stop(); + } + + private String sendGetRequest(String query) throws Exception { + String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8.name()); + URL url = new URL("http://localhost:" + getHttpPort() + "/graphql?query=" + encodedQuery); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setDoInput(true); + connection.setRequestProperty("Accept", "application/json"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line; + StringBuilder buffer = new StringBuilder(); + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + + return buffer.toString(); + } + + private String sendPostRequest(String query) throws Exception { + URL url = new URL("http://localhost:" + getHttpPort() + "/graphql"); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + + ObjectNode node = JsonNodeFactory.instance.objectNode(); + node.put("query", query); + + try (OutputStream os = connection.getOutputStream()) { + byte[] input = node.toString().getBytes(StandardCharsets.UTF_8.name()); + os.write(input, 0, input.length); + } + + + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line; + StringBuilder buffer = new StringBuilder(); + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + + return buffer.toString(); + } +} diff --git a/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java b/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java index 8f36ca012e..0c77511f1f 100644 --- a/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java +++ b/itests/test/src/test/java/org/apache/karaf/itests/examples/WebSocketExampleTest.java @@ -17,12 +17,7 @@ package org.apache.karaf.itests.examples; import org.apache.karaf.itests.BaseTest; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.apache.karaf.itests.util.SimpleSocket; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.Test; @@ -33,9 +28,6 @@ import org.ops4j.pax.exam.spi.reactors.PerClass; import org.osgi.framework.Bundle; import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static junit.framework.TestCase.assertEquals; @@ -77,44 +69,4 @@ public class WebSocketExampleTest extends BaseTest { client.stop(); } - - @WebSocket - public class SimpleSocket { - - private final CountDownLatch closeLatch; - - private Session session; - - public final List<String> messages; - - public SimpleSocket() { - this.messages = new ArrayList<>(); - this.closeLatch = new CountDownLatch(1); - } - - public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { - return this.closeLatch.await(duration, unit); - } - - @OnWebSocketClose - public void onClose(int statusCode, String reason) { - System.out.println("Closing websocket client"); - session.close(StatusCode.NORMAL, "I'm done"); - this.session = null; - this.closeLatch.countDown(); // trigger latch - } - - @OnWebSocketConnect - public void onConnect(Session session) { - System.out.println("Connecting websocket client"); - this.session = session; - } - - @OnWebSocketMessage - public void onMessage(String msg) { - System.out.println("Received websocket message: " + msg); - messages.add(msg); - } - } - } diff --git a/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java b/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java new file mode 100644 index 0000000000..adc930f657 --- /dev/null +++ b/itests/test/src/test/java/org/apache/karaf/itests/util/SimpleSocket.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.itests.util; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@WebSocket +public class SimpleSocket { + + public final List<String> messages; + private final CountDownLatch closeLatch; + private Session session; + + public SimpleSocket() { + this.messages = new ArrayList<>(); + this.closeLatch = new CountDownLatch(1); + } + + public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { + return this.closeLatch.await(duration, unit); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) { + System.out.println("Closing websocket client"); + session.close(StatusCode.NORMAL, "I'm done"); + this.session = null; + this.closeLatch.countDown(); // trigger latch + } + + @OnWebSocketConnect + public void onConnect(Session session) { + System.out.println("Connecting websocket client"); + this.session = session; + } + + @OnWebSocketMessage + public void onMessage(String msg) { + System.out.println("Received websocket message: " + msg); + messages.add(msg); + } +}