You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/10/09 16:39:40 UTC
[karaf] branch main updated: [KARAF-7567] Add GraphQL example
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 <al...@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);
+ }
+}