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

commit f4e3458f2a0600de95ab2d24428e5a0bc794c556
Author: Bertrand Delacretaz <[email protected]>
AuthorDate: Wed May 27 11:48:15 2020 +0200

    Add tag queries driven by server-side GraphQL
---
 org.apache.sling.graphql.samples.website/pom.xml   |   6 ++
 ...lesDataFetcherProvider.java => ArticleRef.java} |  42 ++++----
 .../website/SamplesDataFetcherProvider.java        |   2 +
 .../samples/website/SeeAlsoDataFetcher.java        |  19 ----
 .../samples/website/TagQueryDataFetcher.java       |  72 +++++++++++++
 .../graphql/samples/website/TagsResource.java      | 118 +++++++++++++++++++++
 .../samples/website/TagsResourceProvider.java      |  50 +++++++++
 .../initial-content/apps/samples/tag/GQLschema.jsp |  39 +++++++
 .../initial-content/apps/samples/tag/json.gql      |  25 +++++
 .../content/graphql-website-demo.html              |   7 ++
 10 files changed, 341 insertions(+), 39 deletions(-)

diff --git a/org.apache.sling.graphql.samples.website/pom.xml 
b/org.apache.sling.graphql.samples.website/pom.xml
index a3cee51..5cd9d8f 100644
--- a/org.apache.sling.graphql.samples.website/pom.xml
+++ b/org.apache.sling.graphql.samples.website/pom.xml
@@ -81,6 +81,12 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>adapter-annotations</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
       <groupId>org.osgi</groupId>
       <artifactId>org.osgi.service.component.annotations</artifactId>
       <scope>provided</scope>
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/ArticleRef.java
similarity index 50%
copy from 
org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
copy to 
org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/ArticleRef.java
index 3b88b4c..02025c6 100644
--- 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/ArticleRef.java
@@ -19,28 +19,30 @@
 
 package org.apache.sling.graphql.samples.website;
 
-import graphql.schema.DataFetcher;
-
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.graphql.api.graphqljava.DataFetcherProvider;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.osgi.service.component.annotations.Component;
+import org.apache.sling.api.resource.ValueMap;
+
+public class ArticleRef {
+    private final String title;
+    private final String path;
+    private final String [] tags;
+
+    ArticleRef(Resource r) {
+        final ValueMap vm = r.adaptTo(ValueMap.class);
+        title = vm == null ? null : vm.get("title", String.class);
+        tags = vm == null ? null : vm.get("tags", String[].class);
+        path = r.getPath();
+    }
 
-import java.io.IOException;
+    public String getTitle() {
+        return title;
+    }
 
-@Component(service = DataFetcherProvider.class, property = { 
"namespace=samples" })
-public class SamplesDataFetcherProvider implements DataFetcherProvider {
+    public String getPath() {
+        return path;
+    }
 
-    @Override
-    public @Nullable DataFetcher<Object> createDataFetcher(@NotNull Resource 
r, @NotNull String name,
-            @Nullable String options, @Nullable String source) throws 
IOException {
-                
-        if(SeeAlsoDataFetcher.NAME.equals(name)) {
-            return new SeeAlsoDataFetcher(r);
-        } else if(ValueMapDataFetcher.NAME.equals(name)) {
-            return new ValueMapDataFetcher(r);
-        }
-        return null;
+    public String [] getTags() {
+        return tags;
     }
-}
+}
\ No newline at end of file
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
index 3b88b4c..1e37fea 100644
--- 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SamplesDataFetcherProvider.java
@@ -40,6 +40,8 @@ public class SamplesDataFetcherProvider implements 
DataFetcherProvider {
             return new SeeAlsoDataFetcher(r);
         } else if(ValueMapDataFetcher.NAME.equals(name)) {
             return new ValueMapDataFetcher(r);
+        } else if(TagQueryDataFetcher.NAME.equals(name)) {
+            return new TagQueryDataFetcher(r);
         }
         return null;
     }
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SeeAlsoDataFetcher.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SeeAlsoDataFetcher.java
index 924a1a5..8711cd4 100644
--- 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SeeAlsoDataFetcher.java
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/SeeAlsoDataFetcher.java
@@ -35,25 +35,6 @@ class SeeAlsoDataFetcher implements DataFetcher<Object> {
 
     private final Resource resource;
 
-    public static class ArticleRef {
-        private final String title;
-        private final String path;
-
-        ArticleRef(Resource r) {
-            final ValueMap vm = r.adaptTo(ValueMap.class);
-            title = vm == null ? null : vm.get("title", String.class);
-            path = r.getPath();
-        }
-
-        public String getTitle() {
-            return title;
-        }
-
-        public String getPath() {
-            return path;
-        }
-    }
-
     SeeAlsoDataFetcher(Resource r) {
         this.resource = r;
     }
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagQueryDataFetcher.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagQueryDataFetcher.java
new file mode 100644
index 0000000..9f28aab
--- /dev/null
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagQueryDataFetcher.java
@@ -0,0 +1,72 @@
+/*
+ * 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.samples.website;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+
+import graphql.schema.DataFetcher;
+import graphql.schema.DataFetchingEnvironment;
+
+class TagQueryDataFetcher implements DataFetcher<Object> {
+
+    public static final String NAME = "tagQuery";
+
+    private final Resource resource;
+
+    TagQueryDataFetcher(Resource r) {
+        this.resource = r;
+    }
+
+    static String jcrQuery(String ... tags) {
+        // Build a query like
+        //  /jcr:root/content/articles//*[@tags = "panel" and @tags = "card"]
+        final StringBuilder sb = new 
StringBuilder("/jcr:root/content/articles//*[");
+        for(int i=0 ; i < tags.length; i++) {
+            if(i > 0) {
+                sb.append(" and ");
+            }
+            sb.append("@tags=\"").append(tags[i]).append("\"");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    @Override
+    public Object get(DataFetchingEnvironment environment) throws Exception {
+        final List<ArticleRef> result = new ArrayList<>();
+        final ValueMap vm = resource.adaptTo(ValueMap.class);
+        if(vm != null) {
+            final String [] tags = vm.get("tags", String[].class);
+            if(tags != null) {
+                final Iterator<Resource> it = 
resource.getResourceResolver().findResources(jcrQuery(tags), "xpath");
+                // TODO should stop/paginate if too many results
+                while(it.hasNext()) {
+                    result.add(new ArticleRef(it.next()));
+                }
+            }
+        }
+        return result.toArray();
+    }
+}
\ No newline at end of file
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResource.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResource.java
new file mode 100644
index 0000000..352c6e2
--- /dev/null
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResource.java
@@ -0,0 +1,118 @@
+/*
+ * 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.samples.website;
+
+import org.apache.sling.adapter.annotations.Adaptable;
+import org.apache.sling.adapter.annotations.Adapter;
+import org.apache.sling.api.resource.AbstractResource;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+@Adaptable(
+    adaptableClass=Resource.class, 
+    adapters={ @Adapter({ValueMap.class})}
+)
+public class TagsResource extends AbstractResource {
+    private final String path;
+    private final Map<String, Object> properties = new HashMap<>();
+    private final ResourceMetadata metadata;
+    private final ResourceResolver resolver;
+
+    public static final String RESOURCE_TYPE = "samples/tag";
+    public static final Pattern VALID_TAG = Pattern.compile("(\\w)+");
+
+    private TagsResource(ResourceResolver resolver, String path, String[] 
tags) {
+        this.path = path;
+        this.metadata = new ResourceMetadata();
+        this.metadata.setResolutionPath(path);
+        this.resolver = resolver;
+        properties.put("tags", tags);
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String getResourceType() {
+        return RESOURCE_TYPE;
+    }
+
+    @Override
+    public String getResourceSuperType() {
+        return null;
+    }
+
+    @Override
+    public ResourceMetadata getResourceMetadata() {
+        return metadata;
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return resolver;
+    }
+
+    private static String [] getTags(String path) {
+        final String [] tags = path.split("\\+");
+        if(tags != null && Arrays.stream(tags).allMatch(tag -> 
VALID_TAG.matcher(tag).matches())) {
+            return tags;
+        }
+        return new String[]{};
+    }
+
+    private static String basename(String path) {
+        if(path.length() < TagsResourceProvider.ROOT_PATH.length()) {
+            return path;
+        }
+        path = path.substring(TagsResourceProvider.ROOT_PATH.length());
+        if(path.startsWith("/")) {
+            path = path.substring(1);
+        }
+        return path;
+    }
+
+    /** If the supplied path is valid, return the corresponding
+     *  TagsResource, otherwise null.
+     */
+    static TagsResource create(ResourceResolver resolver, String path) {
+        final String basename = basename(path);
+        final String [] tags = getTags(basename);
+        return tags.length > 0 ? new TagsResource(resolver, path, tags) : null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if(type == ValueMap.class) {
+            return (AdapterType)new ValueMapDecorator(properties);
+        }
+        return super.adaptTo(type);
+    }
+}
\ No newline at end of file
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResourceProvider.java
 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResourceProvider.java
new file mode 100644
index 0000000..43ce650
--- /dev/null
+++ 
b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/TagsResourceProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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.samples.website;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.osgi.service.component.annotations.Component;
+
+import java.util.Iterator;
+
+@Component(service = ResourceProvider.class, property = { 
ResourceProvider.PROPERTY_NAME + "=TagsResourceProvider",
+        ResourceProvider.PROPERTY_ROOT + "=" + TagsResourceProvider.ROOT_PATH 
})
+public class TagsResourceProvider extends 
ResourceProvider<TagsResourceProvider.UnusedContext> {
+
+    public static final String ROOT_PATH = "/content/tags";
+
+    /** We don't need a context for now */
+    public static class UnusedContext {
+    }
+
+    @Override
+    public Resource getResource(ResolveContext<UnusedContext> ctx, String 
path, ResourceContext resourceContext, Resource parent) {
+        return TagsResource.create(ctx.getResourceResolver(), path);
+    }
+
+    @Override
+    public Iterator<Resource> listChildren(ResolveContext<UnusedContext> ctx, 
Resource parent) {
+        // Tags resources are a flat hierarchy with no children
+        return null;
+    };
+}
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/GQLschema.jsp
 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/GQLschema.jsp
new file mode 100644
index 0000000..bb1c0c0
--- /dev/null
+++ 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/GQLschema.jsp
@@ -0,0 +1,39 @@
+<%-- 
+* 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.
+--%>
+
+<%--
+Generating the schemas in JSP might not be the best way
+but it works for these initial tests - we might create
+a "passthrough" script engine, or one that extracts the
+additional DataFetcher information that we need.
+--%>
+
+type Query {
+  # Convert the current Resource to its valueMap
+  # to be able to use the default graphql-java 
+  # PropertiesDataFetcher on (most of) its values
+  ## fetch:samples/tagQuery
+  articlesByTag : [ArticleRef]
+}
+
+type ArticleRef {
+  title: String
+  tags: [String]
+  path: String
+}
\ No newline at end of file
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
new file mode 100644
index 0000000..5e43f64
--- /dev/null
+++ 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
@@ -0,0 +1,25 @@
+# * 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.
+
+# Define the query for this resource type, selector and extension
+{ 
+  articlesByTag {
+    path
+    tags
+    title
+  } 
+}
\ No newline at end of file
diff --git 
a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/graphql-website-demo.html
 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/graphql-website-demo.html
index 1b70d0c..185e42b 100644
--- 
a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/graphql-website-demo.html
+++ 
b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/graphql-website-demo.html
@@ -32,6 +32,13 @@
               The corresponding scripts are in the <a 
href="/bin/browser.html/apps/samples/article">/apps/samples/article</a> folder.
           </li>
           <li>
+            <a href="/content/tags/card+panel.json">Tag queries using a custom 
ResourceResolver + server-side GraphQL queries</a>
+            - raw JSON <a href="/content/tags/card+panel.rawjson">here</a>.
+            <br/>
+            Here's <a 
href="/content/tags/card+alarm+sensor+firewall+application+panel.json">a query 
with few results</a>
+            and its <a 
href="/content/tags/card+alarm+sensor+firewall+application+panel.rawjson"">raw 
JSON</a>.
+          </li>
+          <li>
             <a href="/graphql.json">GraphQL servlet</a>
           </li>
           <li>

Reply via email to