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

Reply via email to