This is an automated email from the ASF dual-hosted git repository.
bdelacretaz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
The following commit(s) were added to refs/heads/master by this push:
new 9d46c72 SLING-10551 - initial implementation, work in progress
9d46c72 is described below
commit 9d46c724ade5568093249ffa703178e60b142194
Author: Bertrand Delacretaz <[email protected]>
AuthorDate: Tue Jul 6 17:59:49 2021 +0200
SLING-10551 - initial implementation, work in progress
---
sling-org-apache-sling-graphql-schema/pom.xml | 288 +++++++++++++++++++++
.../aggregator/api/PartialSchemaProvider.java | 38 +++
.../schema/aggregator/api/SchemaAggregator.java | 31 +++
.../aggregator/impl/BundleEntrySchemaProvider.java | 92 +++++++
.../aggregator/impl/DefaultSchemaAggregator.java | 88 +++++++
.../aggregator/impl/ProviderBundleTracker.java | 84 ++++++
.../servlet/SchemaAggregatorServlet.java | 91 +++++++
.../impl/DefaultSchemaAggregatorTest.java | 80 ++++++
.../aggregator/impl/ProviderBundleTrackerTest.java | 79 ++++++
.../sling/graphql/schema/aggregator/impl/U.java | 54 ++++
.../aggregator/it/SchemaAggregatorServletIT.java | 60 +++++
.../aggregator/it/SchemaAggregatorTestSupport.java | 150 +++++++++++
.../src/test/resources/logback.xml | 31 +++
.../src/test/resources/two-providers-output.txt | 27 ++
14 files changed, 1193 insertions(+)
diff --git a/sling-org-apache-sling-graphql-schema/pom.xml
b/sling-org-apache-sling-graphql-schema/pom.xml
new file mode 100644
index 0000000..7329b3b
--- /dev/null
+++ b/sling-org-apache-sling-graphql-schema/pom.xml
@@ -0,0 +1,288 @@
+<?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.
+-->
+<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">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling-bundle-parent</artifactId>
+ <version>43</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.graphql.schema.aggregator</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Sling GraphQL Schema Aggregator</name>
+ <description>Builds GraphQL Schemas from partials provided by OSGi
bundles</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <org.ops4j.pax.exam.version>4.13.3</org.ops4j.pax.exam.version>
+
<site.javadoc.exclude>org.apache.sling.graphql.schema.aggregator.*</site.javadoc.exclude>
+ <!-- additional options that can be passed to Pax before executing the
tests -->
+ <pax.vm.options />
+ </properties>
+
+ <!--
+ <scm>
+
<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git</connection>
+
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git</developerConnection>
+
<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-graphql-core.git</url>
+ <tag>org.apache.sling.graphql.core-0.0.2</tag>
+ </scm>
+ -->
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-baseline-maven-plugin</artifactId>
+ <configuration>
+ <!-- TODO remove this once we have a release of this module -->
+ <failOnMissing>false</failOnMissing>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemPropertyVariables>
+
<bundle.filename>${basedir}/target/${project.build.finalName}.jar</bundle.filename>
+ <pax.vm.options>${pax.vm.options}</pax.vm.options>
+ </systemPropertyVariables>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ <!-- pax exam bug, often times out at exit -->
+
<forkedProcessExitTimeoutInSeconds>1</forkedProcessExitTimeoutInSeconds>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>src/test/resources/two-providers-output.txt</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>6.0.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains</groupId>
+ <artifactId>annotations</artifactId>
+ <version>16.0.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.18.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.engine</artifactId>
+ <version>2.6.22</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.resource.presence</artifactId>
+ <version>0.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.10.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.servlet-helpers</artifactId>
+ <version>1.4.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-inline</artifactId>
+ <version>3.5.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.sling-mock.junit4</artifactId>
+ <version>2.4.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.paxexam</artifactId>
+ <version>3.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-cm</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-wrap</artifactId>
+ <version>2.3.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.servicemix.bundles</groupId>
+ <artifactId>org.apache.servicemix.bundles.hamcrest</artifactId>
+ <version>1.3_1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient-osgi</artifactId>
+ <version>4.5.10</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.2.3</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <stylesheet>maven</stylesheet>
+
<excludePackageNames>*.core:*.impl:*.internal:${site.javadoc.exclude}</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+
+</project>
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/PartialSchemaProvider.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/PartialSchemaProvider.java
new file mode 100644
index 0000000..95cc55b
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/PartialSchemaProvider.java
@@ -0,0 +1,38 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.graphql.schema.aggregator.api;
+
+import java.io.Reader;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/** A provider of partial OSGI schemas */
+@ProviderType
+public interface PartialSchemaProvider {
+ /** A unique name for this provider */
+ @NotNull String getName();
+
+ /** Return a Reader that provides the contents of the specific schema
section, like "query" or "mutation" */
+ @NotNull Reader getSectionContent(String sectionName);
+
+ /** Return a Reader that provides the contents of the rest of the schema,
what's not in specific sections */
+ @NotNull Reader getBodyContent();
+}
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/SchemaAggregator.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/SchemaAggregator.java
new file mode 100644
index 0000000..540adfb
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/api/SchemaAggregator.java
@@ -0,0 +1,31 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.graphql.schema.aggregator.api;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+@ConsumerType
+public interface SchemaAggregator {
+ /** Writes an aggregated Schema to the supplied Writer */
+ void aggregate(Writer target, String ... providerNames) throws IOException;
+}
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntrySchemaProvider.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntrySchemaProvider.java
new file mode 100644
index 0000000..15388ed
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntrySchemaProvider.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.sling.graphql.schema.aggregator.impl;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
+
+import org.apache.sling.graphql.schema.aggregator.api.PartialSchemaProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@PartialSchemaProvider} build out of a Bundle entry, which must be a valid
+ * partial schema file.
+ */
+class BundleEntrySchemaProvider implements PartialSchemaProvider {
+ private static final Logger log =
LoggerFactory.getLogger(BundleEntrySchemaProvider.class.getName());
+ private final URL url;
+ private final String key;
+ private final long bundleId;
+
+ private BundleEntrySchemaProvider(Bundle b, URL bundleEntry) {
+ this.url = bundleEntry;
+ this.bundleId = b.getBundleId();
+ this.key = String.format("%s(%d):%s", b.getSymbolicName(),
b.getBundleId(), bundleEntry.toString());
+ }
+
+ static BundleEntrySchemaProvider forBundle(Bundle b, String entryPath) {
+ final URL entry = b.getEntry(entryPath);
+ if(entry == null) {
+ log.info("Entry {} not found for bundle {}", entryPath,
b.getSymbolicName());
+ return null;
+ } else {
+ // TODO validate entry?
+ return new BundleEntrySchemaProvider(b, entry);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if(other instanceof BundleEntrySchemaProvider) {
+ return ((BundleEntrySchemaProvider)other).key.equals(key);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ public String toString() {
+ return String.format("%s: %s", getClass().getSimpleName(), key);
+ }
+
+ public String getName() {
+ return url.toString();
+ }
+
+ public long getBundleId() {
+ return bundleId;
+ }
+
+ @Override
+ public @NotNull Reader getSectionContent(String sectionName) {
+ return new StringReader(String.format("Fake section %s for %s",
sectionName, key));
+ }
+
+ @Override
+ public @NotNull Reader getBodyContent() {
+ return new StringReader(String.format("Fake body for %s", key));
+ }
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
new file mode 100644
index 0000000..1b63c1a
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
@@ -0,0 +1,88 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.graphql.schema.aggregator.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.graphql.schema.aggregator.api.PartialSchemaProvider;
+import org.apache.sling.graphql.schema.aggregator.api.SchemaAggregator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+
+@Component(service = SchemaAggregator.class)
+public class DefaultSchemaAggregator implements SchemaAggregator {
+ private ProviderBundleTracker tracker;
+
+ @Activate
+ public void activate(BundleContext ctx) {
+ tracker = new ProviderBundleTracker(ctx);
+ }
+
+ private void copySection(List<PartialSchemaProvider> selected, String
sectionName, Writer target) throws IOException {
+ target.write(String.format("%n%s {%n", sectionName));
+ for(PartialSchemaProvider p : selected) {
+ writeSourceInfo(target, p);
+ IOUtils.copy(p.getSectionContent(sectionName), target);
+ target.write(String.format("%n"));
+ }
+ target.write(String.format("%n}%n"));
+ }
+
+ private void writeSourceInfo(Writer target, PartialSchemaProvider psp)
throws IOException {
+ target.write(String.format("%n# %s.source=%s%n",
getClass().getSimpleName(), psp.getName()));
+ }
+
+ public void aggregate(Writer target, String ...providerNames) throws
IOException {
+ final String info = String.format("Schema aggregated by %s%n",
getClass().getSimpleName());
+ target.write(String.format("# %s", info));
+
+ // build list of selected providers
+ final Map<String, PartialSchemaProvider> providers =
tracker.getSchemaProviders();
+ final List<PartialSchemaProvider> selected = new ArrayList<>();
+ final List<String> missing = new ArrayList<>();
+ for(String provider : providerNames) {
+ final PartialSchemaProvider psp = providers.get(provider);
+ if(psp == null) {
+ missing.add(provider);
+ continue;
+ }
+ selected.add(psp);
+ }
+
+ if(!missing.isEmpty()) {
+ throw new IOException(String.format("Missing providers: %s",
missing));
+ }
+
+ // copy sections
+ copySection(selected, "query", target);
+ copySection(selected, "mutation", target);
+ for(PartialSchemaProvider p : selected) {
+ writeSourceInfo(target, p);
+ IOUtils.copy(p.getBodyContent(), target);
+ }
+ target.write(String.format("%n# End of %s", info));
+ }
+}
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
new file mode 100644
index 0000000..5349ce7
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
@@ -0,0 +1,84 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.graphql.schema.aggregator.impl;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.sling.graphql.schema.aggregator.api.PartialSchemaProvider;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.util.tracker.BundleTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Tracks bundles which provide partial schemas, and registers
+ * a {@PartialSchemaProvider} service for each partial schema
+ */
+public class ProviderBundleTracker extends BundleTracker<Object> {
+
+ public static final String SCHEMA_PATH_HEADER = "Sling-GraphQL-Schema";
+
+ private final Logger log = LoggerFactory.getLogger(getClass().getName());
+ private final Map<String, BundleEntrySchemaProvider> schemaProviders;
+
+ public ProviderBundleTracker(BundleContext context) {
+ super(context, Bundle.ACTIVE, null);
+ schemaProviders = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+ final String providersPath =
bundle.getHeaders().get(SCHEMA_PATH_HEADER);
+ if(providersPath == null) {
+ log.debug("Bundle {} has no {} header, ignored",
bundle.getSymbolicName(), SCHEMA_PATH_HEADER);
+ } else {
+ final Enumeration<String> paths =
bundle.getEntryPaths(providersPath);
+ if(paths != null) {
+ while(paths.hasMoreElements()) {
+ final BundleEntrySchemaProvider a =
BundleEntrySchemaProvider.forBundle(bundle, paths.nextElement());
+ if(a != null) {
+ log.info("Registering {}", a);
+ schemaProviders.put(a.getName(), a);
+ }
+ }
+ }
+ }
+ return super.addingBundle(bundle, event);
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, Object object)
{
+ final long id = bundle.getBundleId();
+ schemaProviders.entrySet().forEach(entry -> {
+ if(id == entry.getValue().getBundleId()) {
+ log.info("Removing {}", entry.getValue());
+ schemaProviders.remove(entry.getKey());
+ }
+ });
+ }
+
+ Map<String, PartialSchemaProvider> getSchemaProviders() {
+ return Collections.unmodifiableMap(schemaProviders);
+ }
+}
diff --git
a/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/servlet/SchemaAggregatorServlet.java
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/servlet/SchemaAggregatorServlet.java
new file mode 100644
index 0000000..a031b69
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/main/java/org/apache/sling/graphql/schema/aggregator/servlet/SchemaAggregatorServlet.java
@@ -0,0 +1,91 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.graphql.schema.aggregator.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
+import org.apache.sling.graphql.schema.aggregator.api.SchemaAggregator;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(
+ service = Servlet.class,
+ name =
"org.apache.sling.graphql.schema.aggregator.SchemaAggregatorServlet",
+ immediate = true,
+ configurationPolicy=ConfigurationPolicy.REQUIRE,
+ property = {
+ "service.description=Sling GraphQL Schema Aggregator Servlet",
+ "service.vendor=The Apache Software Foundation"
+ })
+@Designate(ocd = SchemaAggregatorServlet.Config.class, factory=true)
+
+public class SchemaAggregatorServlet extends SlingSafeMethodsServlet {
+
+ @ObjectClassDefinition(
+ name = "Apache Sling GraphQL Schema Aggregator Servlet",
+ description = "Servlet that aggregates GraphQL schemas")
+ public @interface Config {
+ @AttributeDefinition(
+ name = "Selectors",
+ description="Standard Sling servlet property")
+ String[] sling_servlet_selectors() default "";
+
+ @AttributeDefinition(
+ name = "Resource Types",
+ description="Standard Sling servlet property")
+ String[] sling_servlet_resourceTypes() default "sling/servlet/default";
+
+ @AttributeDefinition(
+ name = "Methods",
+ description="Standard Sling servlet property")
+ String[] sling_servlet_methods() default "GET";
+
+ @AttributeDefinition(
+ name = "Extensions",
+ description="Standard Sling servlet property")
+ String[] sling_servlet_extensions() default "GQLschema";
+ }
+
+ @Reference
+ private transient SchemaAggregator aggregator;
+
+ @Override
+ public void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws IOException {
+ final String [] selectors =
request.getRequestPathInfo().getSelectors();
+ if(selectors.length < 1) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing
required schema selector");
+ return;
+ }
+
+ response.setContentType("text/plain");
+ response.setCharacterEncoding("UTF-8");
+ aggregator.aggregate(response.getWriter(), selectors);
+ }
+}
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
new file mode 100644
index 0000000..5dec881
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.sling.graphql.schema.aggregator.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import static org.mockito.Mockito.mock;
+
+public class DefaultSchemaAggregatorTest {
+ private DefaultSchemaAggregator dsa;
+ private ProviderBundleTracker tracker;
+
+ @Before
+ public void setup() throws Exception {
+ dsa = new DefaultSchemaAggregator();
+ final BundleContext ctx = mock(BundleContext.class);
+ dsa.activate(ctx);
+ final Field f = dsa.getClass().getDeclaredField("tracker");
+ f.setAccessible(true);
+ tracker = (ProviderBundleTracker)f.get(dsa);
+ }
+
+ private void assertContainsIgnoreCase(String substring, String source) {
+ assertTrue("Expecting '" + substring + "' in source string ",
source.toLowerCase().contains(substring.toLowerCase()));
+ }
+
+ @Test
+ public void noProviders() throws Exception{
+ final StringWriter target = new StringWriter();
+ final IOException iox = assertThrows(IOException.class, () ->
dsa.aggregate(target, "Aprov", "Bprov"));
+ assertContainsIgnoreCase("missing providers", iox.getMessage());
+ assertContainsIgnoreCase("Aprov", iox.getMessage());
+ assertContainsIgnoreCase("Bprov", iox.getMessage());
+ assertContainsIgnoreCase("schema aggregated by
DefaultSchemaAggregator", target.toString());
+ }
+
+ @Test
+ public void twoProviders() throws Exception{
+ final StringWriter target = new StringWriter();
+ tracker.addingBundle(U.testBundle("A", 1, 4), null);
+ tracker.addingBundle(U.testBundle("B", 2, 2), null);
+ dsa.aggregate(target, "B/path/2/resource/1", "A/path/1/resource/3");
+ assertContainsIgnoreCase("schema aggregated by
DefaultSchemaAggregator", target.toString());
+
+ try(InputStream is =
getClass().getResourceAsStream("/two-providers-output.txt")) {
+ assertNotNull("Expecting test resource to be present", is);
+ final String expected = IOUtils.toString(is, "UTF-8");
+ assertEquals(expected, target.toString().trim());
+ }
+ }
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
new file mode 100644
index 0000000..bb2a5df
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.sling.graphql.schema.aggregator.impl;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.graphql.schema.aggregator.api.PartialSchemaProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+
+public class ProviderBundleTrackerTest {
+ private ProviderBundleTracker tracker;
+ private static long bundleId;
+
+ @Before
+ public void setup() {
+ bundleId = 0;
+ final BundleContext ctx = mock(BundleContext.class);
+ tracker = new ProviderBundleTracker(ctx);
+ }
+
+ @Test
+ public void addBundle() {
+ final Bundle a = U.testBundle("A", ++bundleId, 1);
+ tracker.addingBundle(a, null);
+ assertEquals(1, tracker.getSchemaProviders().size());
+
+ final PartialSchemaProvider sp =
tracker.getSchemaProviders().values().iterator().next();
+ assertTrue(sp.toString().contains(a.getSymbolicName()));
+ assertTrue(sp.toString().contains("resource/1"));
+ }
+
+ @Test
+ public void addAndRemoveBundles() {
+ final Bundle a = U.testBundle("A", ++bundleId, 1);
+ final Bundle b = U.testBundle("B", ++bundleId, 1);
+ tracker.addingBundle(a, null);
+ tracker.addingBundle(b, null);
+ assertEquals(2, tracker.getSchemaProviders().size());
+ tracker.removedBundle(b, null, null);
+ assertEquals(1, tracker.getSchemaProviders().size());
+ tracker.removedBundle(a, null, null);
+ assertEquals(0, tracker.getSchemaProviders().size());
+ tracker.removedBundle(a, null, null);
+ assertEquals(0, tracker.getSchemaProviders().size());
+ }
+
+ @Test
+ public void getSectionsContent() throws IOException {
+ final Bundle a = U.testBundle("A", ++bundleId, 1);
+ tracker.addingBundle(a, null);
+ final PartialSchemaProvider psp =
tracker.getSchemaProviders().values().iterator().next();
+ assertEquals("Fake section S1 for A(1):A/path/1/resource/1",
IOUtils.toString(psp.getSectionContent("S1")));
+ assertEquals("Fake body for A(1):A/path/1/resource/1",
IOUtils.toString(psp.getBodyContent()));
+ }
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/U.java
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/U.java
new file mode 100644
index 0000000..0ab3ff6
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/U.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.sling.graphql.schema.aggregator.impl;
+
+import org.osgi.framework.Bundle;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+class U {
+ static Bundle testBundle(String symbolicName, long id, int nEntries) {
+ final Bundle b = mock(Bundle.class);
+ when(b.getSymbolicName()).thenReturn(symbolicName);
+ when(b.getBundleId()).thenReturn(id);
+
+ final Dictionary<String, String> headers = new Hashtable<>();
+ String fakePath = symbolicName + "/path/" + id;
+ headers.put(ProviderBundleTracker.SCHEMA_PATH_HEADER, fakePath);
+ when(b.getHeaders()).thenReturn(headers);
+
+ final List<String> resources = new ArrayList<>();
+ for(int i=1 ; i <= nEntries; i++) {
+ String fakeResource = fakePath + "/resource/" + i;
+ resources.add(fakeResource);
+ final URL url = mock(URL.class);
+ when(url.toString()).thenReturn(fakeResource);
+ when(b.getEntry(fakeResource)).thenReturn(url);
+ }
+
when(b.getEntryPaths(fakePath)).thenReturn(Collections.enumeration(resources));
+ return b;
+ }
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorServletIT.java
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorServletIT.java
new file mode 100644
index 0000000..25d9ddc
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorServletIT.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sling.graphql.schema.aggregator.it;
+
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+import static
org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SchemaAggregatorServletIT extends SchemaAggregatorTestSupport {
+ private static final String AGGREGATOR_SERVLET_CONFIG_PID =
"org.apache.sling.graphql.schema.aggregator.SchemaAggregatorServlet";
+ private static final String GQL_SCHEMA_EXT = "GQLschema";
+
+ @Configuration
+ public Option[] configuration() {
+ return new Option[]{
+ baseConfiguration(),
+
+ // The aggregator servlet is disabled by default
+ factoryConfiguration(AGGREGATOR_SERVLET_CONFIG_PID)
+ .put("sling.servlet.resourceTypes", "sling/servlet/default")
+ .put("sling.servlet.extensions", GQL_SCHEMA_EXT)
+ .put("sling.servlet.selectors", new String[] { "X", "Y" })
+ .put("sling.servlet.methods", new String[] { "GET" })
+ .asOption(),
+ };
+ }
+
+ @Test
+ public void servletIsActive() throws Exception {
+ // TODO this doesn't actually test the servlet so far
+ //assertEquals("Not a schema yet, for providers [X]", getContent("/."
+ GQL_SCHEMA_EXT));
+ getContent("/.json");
+ }
+
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
new file mode 100644
index 0000000..8163c41
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
@@ -0,0 +1,150 @@
+/*
+ * 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.sling.graphql.schema.aggregator.it;
+
+import java.io.Reader;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.engine.SlingRequestProcessor;
+import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
+import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
+import org.apache.sling.testing.paxexam.TestSupport;
+import org.junit.Before;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static
org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingScripting;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingJsp;
+import static org.junit.Assert.fail;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.when;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+public abstract class SchemaAggregatorTestSupport extends TestSupport {
+
+ private final Logger log = LoggerFactory.getLogger(getClass().getName());
+ private final static int STARTUP_WAIT_SECONDS = 30;
+
+ @Inject
+ protected ResourceResolverFactory resourceResolverFactory;
+
+ @Inject
+ protected SlingRequestProcessor requestProcessor;
+
+ protected ModifiableCompositeOption baseConfiguration() {
+ final String vmOpt = System.getProperty("pax.vm.options");
+ VMOption vmOption = null;
+ if (StringUtils.isNotEmpty(vmOpt)) {
+ vmOption = new VMOption(vmOpt);
+ }
+
+ final String jacocoOpt = System.getProperty("jacoco.command");
+ VMOption jacocoCommand = null;
+ if (StringUtils.isNotEmpty(jacocoOpt)) {
+ jacocoCommand = new VMOption(jacocoOpt);
+ }
+
+ return composite(
+ when(vmOption != null).useOptions(vmOption),
+ when(jacocoCommand != null).useOptions(jacocoCommand),
+ super.baseConfiguration(),
+ slingQuickstart(),
+ testBundle("bundle.filename"),
+
newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
+ .put("whitelist.bundles.regexp", "^PAXEXAM.*$")
+ .asOption(),
+
mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.servlet-helpers").versionAsInProject(),
+ junitBundles()
+ );
+ }
+
+ private Option slingQuickstart() {
+ final int httpPort = findFreePort();
+ log.info("Using HTTP port {}", httpPort);
+ final String workingDirectory = workingDirectory();
+ return composite(
+ slingQuickstartOakTar(workingDirectory, httpPort),
+ slingScripting(),
+ slingScriptingJsp()
+ );
+ }
+
+ /**
+ * Injecting the appropriate services to wait for would be more elegant
but this is very reliable..
+ */
+ @Before
+ public void waitForSling() throws Exception {
+ final int expectedStatus = 200;
+ final List<Integer> statuses = new ArrayList<>();
+ final String path = "/.json";
+ final Instant endTime =
Instant.now().plus(Duration.ofSeconds(STARTUP_WAIT_SECONDS));
+
+ while(Instant.now().isBefore(endTime)) {
+ final int status = executeRequest("GET", path, null, null, null,
-1).getStatus();
+ statuses.add(status);
+ if (status == expectedStatus) {
+ return;
+ }
+ Thread.sleep(250);
+ }
+
+ fail("Did not get a " + expectedStatus + " status at " + path + " got
" + statuses);
+ }
+
+ protected MockSlingHttpServletResponse executeRequest(final String method,
+ final String path, Map<String, Object> params, String contentType,
+ Reader body, final int expectedStatus) throws Exception {
+
+ // Admin resolver is fine for testing
+ @SuppressWarnings("deprecation")
+ final ResourceResolver resourceResolver =
resourceResolverFactory.getAdministrativeResourceResolver(null);
+
+ final int [] statusParam = expectedStatus == -1 ? null : new int[] {
expectedStatus };
+
+ return (MockSlingHttpServletResponse)
+ new SlingInternalRequest(resourceResolver, requestProcessor, path)
+ .withRequestMethod(method)
+ .withParameters(params)
+ .withContentType(contentType)
+ .withBody(body)
+ .execute()
+ .checkStatus(statusParam)
+ .getResponse()
+ ;
+ }
+
+ protected String getContent(String path) throws Exception {
+ return executeRequest("GET", path, null, null, null,
200).getOutputAsString();
+ }
+}
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/resources/logback.xml
b/sling-org-apache-sling-graphql-schema/src/test/resources/logback.xml
new file mode 100644
index 0000000..254317a
--- /dev/null
+++ b/sling-org-apache-sling-graphql-schema/src/test/resources/logback.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+<configuration>
+ <appender name="file" class="ch.qos.logback.core.FileAppender">
+ <file>target/test.log</file>
+ <append>true</append>
+ <encoder>
+ <pattern>%date level=%level thread=%thread logger=%logger
sourcefile=%file line=%line %mdc message=%msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="file" />
+ </root>
+
+ <logger name="org.apache.sling.graphql" level="DEBUG"/>
+</configuration>
\ No newline at end of file
diff --git
a/sling-org-apache-sling-graphql-schema/src/test/resources/two-providers-output.txt
b/sling-org-apache-sling-graphql-schema/src/test/resources/two-providers-output.txt
new file mode 100644
index 0000000..b896741
--- /dev/null
+++
b/sling-org-apache-sling-graphql-schema/src/test/resources/two-providers-output.txt
@@ -0,0 +1,27 @@
+# Schema aggregated by DefaultSchemaAggregator
+
+query {
+
+# DefaultSchemaAggregator.source=B/path/2/resource/1
+Fake section query for B(2):B/path/2/resource/1
+
+# DefaultSchemaAggregator.source=A/path/1/resource/3
+Fake section query for A(1):A/path/1/resource/3
+
+}
+
+mutation {
+
+# DefaultSchemaAggregator.source=B/path/2/resource/1
+Fake section mutation for B(2):B/path/2/resource/1
+
+# DefaultSchemaAggregator.source=A/path/1/resource/3
+Fake section mutation for A(1):A/path/1/resource/3
+
+}
+
+# DefaultSchemaAggregator.source=B/path/2/resource/1
+Fake body for B(2):B/path/2/resource/1
+# DefaultSchemaAggregator.source=A/path/1/resource/3
+Fake body for A(1):A/path/1/resource/3
+# End of Schema aggregated by DefaultSchemaAggregator
\ No newline at end of file