View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2019 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.wcm.ui.granite.pathfield.impl;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.regex.Pattern;
29  
30  import javax.jcr.Node;
31  import javax.jcr.RepositoryException;
32  import javax.servlet.Servlet;
33  import javax.servlet.ServletException;
34  
35  import org.apache.commons.collections4.IteratorUtils;
36  import org.apache.commons.collections4.Predicate;
37  import org.apache.commons.collections4.PredicateUtils;
38  import org.apache.commons.collections4.Transformer;
39  import org.apache.commons.collections4.iterators.FilterIterator;
40  import org.apache.commons.collections4.iterators.TransformIterator;
41  import org.apache.sling.api.SlingHttpServletRequest;
42  import org.apache.sling.api.SlingHttpServletResponse;
43  import org.apache.sling.api.resource.Resource;
44  import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
45  import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
46  import org.jetbrains.annotations.NotNull;
47  import org.osgi.framework.BundleContext;
48  import org.osgi.service.component.annotations.Activate;
49  import org.osgi.service.component.annotations.Component;
50  import org.osgi.service.component.annotations.Reference;
51  
52  import com.adobe.granite.ui.components.Config;
53  import com.adobe.granite.ui.components.ExpressionHelper;
54  import com.adobe.granite.ui.components.ExpressionResolver;
55  import com.adobe.granite.ui.components.PagingIterator;
56  import com.adobe.granite.ui.components.ds.AbstractDataSource;
57  import com.adobe.granite.ui.components.ds.DataSource;
58  import com.adobe.granite.ui.components.ds.EmptyDataSource;
59  
60  import io.wcm.wcm.ui.granite.pathfield.impl.predicate.HideInternalContentPathsPredicate;
61  import io.wcm.wcm.ui.granite.pathfield.impl.util.PredicatedResourceWrapper;
62  import io.wcm.wcm.ui.granite.util.impl.PredicateProviderUtils;
63  
64  /**
65   * Servlet implementing the data source for the path field widget.
66   */
67  @Component(service = Servlet.class)
68  @SlingServletResourceTypes(
69      resourceTypes = PathFieldChildrenDatasourceServlet.RESOURCE_TYPE)
70  public class PathFieldChildrenDatasourceServlet extends SlingSafeMethodsServlet {
71  
72    private static final long serialVersionUID = 1L;
73  
74    static final String RESOURCE_TYPE = "wcm-io/wcm/ui/granite/components/form/pathfield/datasources/children";
75  
76    private BundleContext bundleContext;
77    @Reference
78    private ExpressionResolver expressionResolver;
79  
80    @Activate
81    private void activate(BundleContext bc) {
82      this.bundleContext = bc;
83    }
84  
85    @Override
86    @SuppressWarnings("null")
87    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response)
88        throws ServletException, IOException {
89  
90      final ExpressionHelper ex = new ExpressionHelper(expressionResolver, request);
91      final Config cfg = new Config(request.getResource().getChild(Config.DATASOURCE));
92  
93      final String query = ex.getString(cfg.get("query", String.class));
94  
95      final String parentPath;
96      final String searchName;
97  
98      if (query != null) {
99        final String rootPath = ex.getString(cfg.get("rootPath", "/"));
100 
101       final int slashIndex = query.lastIndexOf('/');
102       if (slashIndex < 0) {
103         parentPath = rootPath;
104         searchName = query.toLowerCase();
105       }
106       else if (!query.startsWith(rootPath)) {
107         parentPath = rootPath;
108         searchName = null;
109       }
110       else if (slashIndex == query.length() - 1) {
111         parentPath = query;
112         searchName = null;
113       }
114       else {
115         parentPath = query.substring(0, slashIndex + 1);
116         searchName = query.substring(slashIndex + 1).toLowerCase();
117       }
118     }
119     else {
120       parentPath = ex.getString(cfg.get("path", String.class));
121       searchName = null;
122     }
123 
124     final Resource parent = request.getResourceResolver().getResource(parentPath);
125 
126     final DataSource ds;
127     if (parent == null) {
128       ds = EmptyDataSource.instance();
129     }
130     else {
131       final Integer offset = ex.get(cfg.get("offset", String.class), Integer.class);
132       final Integer limit = ex.get(cfg.get("limit", String.class), Integer.class);
133       final String itemResourceType = cfg.get("itemResourceType", String.class);
134       final String[] filter = new String[] {
135           ex.get(cfg.get("filter", "hierarchyNotFile"), String.class)
136       };
137 
138       final Collection<Predicate<Resource>> predicates = new ArrayList<>();
139       predicates.add(new HideInternalContentPathsPredicate());
140       predicates.addAll(PredicateProviderUtils.toPredicates(filter, bundleContext));
141 
142       if (searchName != null) {
143         final Pattern searchNamePattern = Pattern.compile(Pattern.quote(searchName), Pattern.CASE_INSENSITIVE);
144         predicates.add(resource -> searchNamePattern.matcher(resource.getName()).lookingAt());
145       }
146 
147       final Predicate<Resource> predicate = PredicateUtils.allPredicate(predicates);
148       final Transformer<Resource, Resource> transformer = createTransformer(itemResourceType, predicate);
149 
150       DataSource datasource = new AbstractDataSource() {
151 
152         @Override
153         public Iterator<Resource> iterator() {
154           List<Resource> list = IteratorUtils.toList(new FilterIterator<>(parent.listChildren(), predicate));
155 
156           // sort result set alphabetically - but only if parent node does not have orderable child nodes
157           if (!isOrderableChildNodes(parent)) {
158             Collections.sort(list, (Resource r1, Resource r2) -> r1.getName().compareTo(r2.getName()));
159           }
160 
161           return new TransformIterator<>(new PagingIterator<>(list.iterator(), offset, limit), transformer);
162         }
163       };
164 
165       ds = datasource;
166     }
167 
168     request.setAttribute(DataSource.class.getName(), ds);
169   }
170 
171   private static Transformer<Resource, Resource> createTransformer(final String itemResourceType, final Predicate<Resource> predicate) {
172     return r -> new PredicatedResourceWrapper(r, predicate) {
173 
174       @Override
175       public @NotNull String getResourceType() {
176         if (itemResourceType == null) {
177           return super.getResourceType();
178         }
179         return itemResourceType;
180       }
181     };
182   }
183 
184   private static boolean isOrderableChildNodes(Resource resource) {
185     Node node = resource.adaptTo(Node.class);
186     if (node != null) {
187       try {
188         return node.getPrimaryNodeType().hasOrderableChildNodes();
189       }
190       catch (RepositoryException ex) {
191         return false;
192       }
193     }
194     return false;
195   }
196 
197 }