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>
