View Javadoc
1   /**
2    * This file Copyright (c) 2011-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.workbench.tree;
35  
36  import info.magnolia.cms.util.QueryUtil;
37  import info.magnolia.context.MgnlContext;
38  import info.magnolia.jcr.RuntimeRepositoryException;
39  import info.magnolia.jcr.util.NodeTypes;
40  import info.magnolia.jcr.util.NodeUtil;
41  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinition;
42  import info.magnolia.ui.vaadin.integration.contentconnector.NodeTypeDefinition;
43  import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
44  import info.magnolia.ui.vaadin.integration.jcr.JcrItemId;
45  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
46  import info.magnolia.ui.workbench.container.AbstractJcrContainer;
47  import info.magnolia.ui.workbench.container.OrderBy;
48  
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.Comparator;
53  import java.util.List;
54  
55  import javax.jcr.Item;
56  import javax.jcr.Node;
57  import javax.jcr.NodeIterator;
58  import javax.jcr.Property;
59  import javax.jcr.PropertyIterator;
60  import javax.jcr.RepositoryException;
61  import javax.jcr.Session;
62  import javax.jcr.nodetype.NodeType;
63  import javax.jcr.query.Query;
64  
65  import org.apache.commons.lang3.StringUtils;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  import com.vaadin.v7.data.Container;
70  
71  /**
72   * Hierarchical implementation of {@link info.magnolia.ui.workbench.container.AbstractJcrContainer}.
73   */
74  public class HierarchicalJcrContainer extends AbstractJcrContainer implements Container.Hierarchical {
75  
76      private static final Logger log = LoggerFactory.getLogger(HierarchicalJcrContainer.class);
77  
78      private static final String WHERE_CLAUSE_FOR_PATH = " ISCHILDNODE('%s')";
79  
80      private int rootDepth = -1;
81      private String nodePath;
82  
83      private boolean sortable;
84  
85      private boolean isIncludingSystemProperties = false;
86  
87      public HierarchicalJcrContainer(JcrContentConnectorDefinition definition) {
88          super(definition);
89      }
90  
91      @Override
92      public Collection<JcrItemId> getChildren(Object itemId) {
93          long start = System.currentTimeMillis();
94          Collection<Item> children = getChildren(getJcrItem(itemId));
95          log.debug("Fetched {} children in {}ms", children.size(), System.currentTimeMillis() - start);
96          return createContainerIds(children);
97      }
98  
99      @Override
100     public JcrItemId getParent(Object itemId) {
101         try {
102             Item item = getJcrItem(itemId);
103             int itemDepth = item.getDepth();
104             // Return null if the item is root
105             if (itemDepth == 0) {
106                 return null;
107             }
108 
109             if (item.isNode() && itemDepth == getRootDepth() + 1) {
110                 return null;
111             }
112             return JcrItemUtil.getItemId(item.getParent());
113         } catch (RepositoryException e) {
114             handleRepositoryException(log, "Cannot determine parent for itemId: " + itemId, e);
115             return null;
116         }
117     }
118 
119     @Override
120     public Collection<JcrItemId> rootItemIds() {
121         try {
122             return createContainerIds(getRootItemIds());
123         } catch (RepositoryException e) {
124             handleRepositoryException(log, "Cannot retrieve root item id's", e);
125             return Collections.emptySet();
126         }
127     }
128 
129     @Override
130     public void refresh() {
131         resetOffset();
132         clearItemIndexes();
133         fireItemSetChange();
134     }
135 
136     @Override
137     public boolean setParent(Object itemId, Object newParentId) throws UnsupportedOperationException {
138         fireItemSetChange();
139         return true;
140     }
141 
142     @Override
143     public boolean areChildrenAllowed(Object itemId) {
144         final JcrItemAdapter./../../info/magnolia/ui/vaadin/integration/jcr/JcrItemAdapter.html#JcrItemAdapter">JcrItemAdapter item = ((JcrItemAdapter) getItem(itemId));
145         if (item == null) {
146             return false;
147         }
148         return item.isNode() && hasChildren(itemId);
149     }
150 
151     @Override
152     public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) throws UnsupportedOperationException {
153         throw new UnsupportedOperationException();
154     }
155 
156     @Override
157     public boolean isRoot(Object itemId) {
158         try {
159             return isRoot(getJcrItem(itemId));
160         } catch (RepositoryException e) {
161             handleRepositoryException(log, "Cannot determine whether item is root - itemId: " + itemId, e);
162             return true;
163         }
164     }
165 
166     @Override
167     public boolean hasChildren(Object itemId) {
168         final Item item = getJcrItem(itemId);
169         if (item.isNode()) {
170             final Node node = (Node) item;
171             try {
172                 final NodeIterator it = node.getNodes();
173                 while (it.hasNext()) {
174                     if (isNodeVisible(it.nextNode())) {
175                         return true;
176                     }
177                 }
178             } catch (RepositoryException e) {
179                 log.warn("Failed to get child nodes of {}", NodeUtil.getPathIfPossible((node)));
180             }
181 
182 
183             if (getConfiguration().isIncludeProperties()) {
184                 try {
185                     final PropertyIterator propertyIterator = node.getProperties();
186                     while (propertyIterator.hasNext()) {
187                         final Property property = propertyIterator.nextProperty();
188                         if (isIncludingSystemProperties || !isJcrOrMgnlProperty(property))
189                             return true;
190                     }
191                 } catch (RepositoryException e) {
192                     log.warn("Failed to get child nodes of {}", NodeUtil.getPathIfPossible((node)));
193                 }
194             }
195         }
196         return false;
197     }
198 
199     @Override
200     public void sort(final Object[] propertyId, final boolean[] ascending) {
201         sorters.clear();
202         for (int i = 0; i < propertyId.length; i++) {
203             if (getSortableContainerPropertyIds().contains(String.valueOf(propertyId[i]))) {
204                 OrderBycontainer/OrderBy.html#OrderBy">OrderBy orderBy = new OrderBy((String) propertyId[i], ascending[i]);
205                 sorters.add(orderBy);
206             }
207         }
208         if (sorters.isEmpty()) {
209             fireItemSetChange();
210         } else {
211             fireItemSetChange(new ItemsSortedEvent(this));
212         }
213     }
214 
215     public void setIncludeSystemProperties(boolean includingSystemProperties) {
216         boolean currentlyIncludingSystemProperties = this.isIncludingSystemProperties;
217         this.isIncludingSystemProperties = includingSystemProperties;
218 
219         if (currentlyIncludingSystemProperties != includingSystemProperties) {
220             refresh();
221         }
222     }
223 
224     public boolean isIncludingSystemProperties() {
225         return isIncludingSystemProperties;
226     }
227 
228     private boolean isJcrOrMgnlProperty(Property property) throws RepositoryException {
229         final String propertyName = property.getName();
230         return propertyName.startsWith(NodeTypes.JCR_PREFIX) || propertyName.startsWith(NodeTypes.MGNL_PREFIX);
231     }
232 
233     protected Collection<JcrItemId> createContainerIds(Collection<Item> children) {
234         ArrayList<JcrItemId> ids = new ArrayList<>();
235         for (Item child : children) {
236             try {
237                 JcrItemId itemId = JcrItemUtil.getItemId(child);
238                 ids.add(itemId);
239             } catch (RepositoryException e) {
240                 handleRepositoryException(log, "Cannot retrieve currentId", e);
241             }
242         }
243         return ids;
244     }
245 
246     public Collection<Item> getChildren(Item item) {
247         if (!item.isNode()) {
248             return Collections.emptySet();
249         }
250 
251         Node node = (Node) item;
252         ArrayList<Item> items = new ArrayList<Item>();
253         try {
254             NodeIterator iterator;
255             // we cannot use query just for getting children of a node as SQL2 is not returning them in natural order
256             // so if any sorter is defined, or defaultOrder is specified use query, otherwise get all children from the node
257             if (isSortable() && (!sorters.isEmpty() || StringUtils.isNotBlank(getConfiguration().getDefaultOrder()))) {
258                 nodePath = node.getPath();
259                 String query = constructJCRQuery(true);
260                 iterator = QueryUtil.search(getWorkspace(), query, Query.JCR_SQL2);
261             } else {
262                 iterator = node.getNodes();
263             }
264 
265             while (iterator.hasNext()) {
266                 Node next = iterator.nextNode();
267                 if (isNodeVisible(next)) {
268                     items.add(next);
269                 }
270             }
271 
272             if (getConfiguration().isIncludeProperties()) {
273                 ArrayList<Property> properties = new ArrayList<Property>();
274                 PropertyIterator propertyIterator = node.getProperties();
275                 while (propertyIterator.hasNext()) {
276                     final Property property = propertyIterator.nextProperty();
277                     if (isIncludingSystemProperties || !isJcrOrMgnlProperty(property)) {
278                         properties.add(property);
279                     }
280                 }
281                 ItemNameComparator itemNameComparator = new ItemNameComparator();
282                 Collections.sort(properties, itemNameComparator);
283                 items.addAll(properties);
284             }
285         } catch (RepositoryException e) {
286             handleRepositoryException(log, "Could not retrieve children", e);
287         }
288 
289         return Collections.unmodifiableCollection(items);
290     }
291 
292     protected boolean isNodeVisible(Node node) throws RepositoryException {
293 
294         if (!getConfiguration().isIncludeSystemNodes() && node.getName().startsWith("jcr:") || node.getName().startsWith("rep:")) {
295             return false;
296         }
297 
298         String primaryNodeTypeName = node.getPrimaryNodeType().getName();
299         for (NodeTypeDefinition nodeTypeDefinition : getConfiguration().getNodeTypes()) {
300             if (nodeTypeDefinition.isStrict()) {
301                 if (primaryNodeTypeName.equals(nodeTypeDefinition.getName())) {
302                     return true;
303                 }
304             } else if (NodeUtil.isNodeType(node, nodeTypeDefinition.getName())) {
305                 return true;
306             }
307         }
308         return false;
309     }
310 
311     public Collection<Item> getRootItemIds() throws RepositoryException {
312         return getChildren(getRootNode());
313     }
314 
315     /**
316      * Checks if an item is a root. Since root node is never shown, we consider its child nodes and properties as roots
317      * to remove unnecessary offset in trees.
318      */
319     public boolean isRoot(Item item) throws RepositoryException {
320         if (item != null) {
321             try {
322                 return item.getDepth() == getRootDepth() + 1;
323             } catch (RepositoryException e) {
324                 handleRepositoryException(log, "Cannot determine depth of jcr item", e);
325             }
326         }
327         return true;
328     }
329 
330     private int getRootDepth() {
331         if (rootDepth == -1) {
332             try {
333                 rootDepth = getRootNode().getDepth();
334             } catch (RepositoryException e) {
335                 throw new RuntimeRepositoryException(e);
336             }
337         }
338         return rootDepth;
339     }
340 
341     @Override
342     protected String getQueryWhereClause() {
343         String whereClause;
344         String clauseWorkspacePath = getQueryWhereClauseForNodePath(nodePath);
345         String clauseNodeTypes = getQueryWhereClauseNodeTypes();
346         if (StringUtils.isNotBlank(clauseNodeTypes)) {
347             whereClause = " where ((" + clauseNodeTypes + ") ";
348             if (StringUtils.isNotBlank(clauseWorkspacePath)) {
349                 whereClause += "and " + clauseWorkspacePath;
350             }
351             whereClause += ") ";
352         } else {
353             whereClause = " where ";
354         }
355 
356         log.debug("JCR query WHERE clause is {}", whereClause);
357         return whereClause;
358     }
359 
360     private String getQueryWhereClauseForNodePath(final String nodePath) {
361         return String.format(WHERE_CLAUSE_FOR_PATH, nodePath);
362     }
363 
364     @Override
365     protected String getQueryWhereClauseNodeTypes() {
366         List<String> defs = new ArrayList<String>();
367         for (NodeType nt : getSearchableNodeTypes()) {
368             if (nt.isMixin()) {
369                 // Mixin type information is found in jcr:mixinTypes property see http://www.day.com/specs/jcr/2.0/10_Writing.html#10.10.3%20Assigning%20Mixin%20Node%20Types
370                 defs.add("[jcr:mixinTypes] = '" + nt.getName() + "'");
371             } else {
372                 defs.add("[jcr:primaryType] = '" + nt.getName() + "'");
373             }
374         }
375         return StringUtils.join(defs, " or ");
376     }
377 
378     /**
379      * Only used in tests.
380      */
381     String getPathInTree(Item item) throws RepositoryException {
382         String base = getConfiguration().getRootPath();
383         return "/".equals(base) ? item.getPath() : StringUtils.substringAfter(item.getPath(), base);
384     }
385 
386     private Session getSession() throws RepositoryException {
387         return MgnlContext.getJCRSession(getWorkspace());
388     }
389 
390     protected Node getRootNode() throws RepositoryException {
391         return getSession().getNode(getConfiguration().getRootPath());
392     }
393 
394     public boolean isSortable() {
395         return sortable;
396     }
397 
398     public void setSortable(boolean sortable) {
399         this.sortable = sortable;
400     }
401 
402     private static class ItemNameComparator implements Comparator<Item> {
403 
404         @Override
405         public int compare(Item lhs, Item rhs) {
406             try {
407                 return lhs.getName().compareTo(rhs.getName());
408             } catch (RepositoryException e) {
409                 log.warn("Cannot compare item names: " + e);
410                 return 0;
411             }
412         }
413     }
414 
415     /**
416      * Implementation of items sorted event.
417      */
418     protected class ItemsSortedEvent implements Container.ItemSetChangeEvent {
419 
420         Container container;
421 
422         public ItemsSortedEvent(Container container) {
423             super();
424             this.container = container;
425         }
426 
427         @Override
428         public Container getContainer() {
429             return container;
430         }
431     }
432 }