View Javadoc
1   /**
2    * This file Copyright (c) 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.contentapp;
35  
36  import static com.vaadin.shared.data.sort.SortDirection.ASCENDING;
37  import static info.magnolia.ui.contentapp.JcrDataProviderUtils.*;
38  
39  import info.magnolia.context.Context;
40  import info.magnolia.ui.contentapp.observation.JcrDataSourceObservation;
41  import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
42  import info.magnolia.ui.framework.ioc.Destructible;
43  
44  import java.util.Optional;
45  import java.util.stream.Collectors;
46  import java.util.stream.Stream;
47  
48  import javax.inject.Provider;
49  import javax.jcr.Node;
50  import javax.jcr.NodeIterator;
51  import javax.jcr.RepositoryException;
52  import javax.jcr.Session;
53  
54  import org.apache.commons.collections4.CollectionUtils;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  import com.vaadin.data.provider.AbstractBackEndDataProvider;
59  import com.vaadin.data.provider.Query;
60  import com.vaadin.data.provider.SortOrder;
61  
62  import lombok.SneakyThrows;
63  
64  /**
65   * JCR data provider.
66   */
67  public class JcrDataProvider extends AbstractBackEndDataProvider<Node, DataFilter> implements Destructible {
68      private static final Logger log = LoggerFactory.getLogger(JcrDataProvider.class);
69      private final JcrDatasourceDefinition definition;
70      private final Provider<Context> contextProvider;
71      private final JcrDataSourceObservation observation;
72  
73      public JcrDataProvider(JcrDatasourceDefinition definition, JcrDataSourceObservation observation, Provider<Context> contextProvider) {
74          this.definition = definition;
75          this.contextProvider = contextProvider;
76          this.observation = observation;
77          observation.register(definition,this);
78      }
79  
80      @Override
81      public void destroy() {
82          observation.destroy();
83      }
84  
85      @Override
86      public boolean isInMemory() {
87          return false;
88      }
89  
90      @Override
91      @SneakyThrows
92      public Stream<Node> fetchFromBackEnd(Query<Node, DataFilter> query) {
93          javax.jcr.query.Query q = createJcrQuery(query);
94          q.setLimit(query.getLimit());
95          q.setOffset(query.getOffset());
96  
97          final NodeIterator result = q.execute().getNodes();
98          //noinspection unchecked
99          return streamQueryResults(query, result);
100     }
101 
102     @Override
103     @SneakyThrows
104     public int sizeInBackEnd(Query<Node, DataFilter> query) {
105         return (int)createJcrQuery(query).execute().getNodes().getSize();
106     }
107 
108     @Override
109     @SneakyThrows
110     public Object getId(Node item) {
111         return item.getIdentifier();
112     }
113 
114     private Session getSession() throws RepositoryException {
115         return contextProvider.get().getJCRSession(definition.getWorkspace());
116     }
117 
118     @SneakyThrows
119     private javax.jcr.query.Query createJcrQuery(Query<Node, DataFilter> query) {
120         final StringBuilder statement = new StringBuilder("select * from [nt:base] as t ");
121 
122         createWhereClause().ifPresent(clause -> statement.append(clause));
123         createOrderByClause(query).ifPresent(clause -> statement.append(clause));
124 
125         log.debug("JCR query statement is {}", statement);
126 
127         return getSession()
128                         .getWorkspace()
129                         .getQueryManager()
130                         .createQuery(statement.toString(), javax.jcr.query.Query.JCR_SQL2);
131     }
132 
133     private Stream<Node> streamQueryResults(Query<Node, DataFilter> query, NodeIterator nodes) {
134         //noinspection unchecked
135         return streamJcrItems(nodes)
136                 .map(node -> wrap((Node) node))
137                 .filter(node ->
138                         query.getFilter()
139                                 .map(DataFilter::getPathFilter)
140                                 .map(path -> isItemPathMatching((Node) node, path))
141                                 .orElse(true));
142     }
143 
144     private Optional<String> createWhereClause() {
145         if (CollectionUtils.isEmpty(definition.getAllowedNodeTypes())) {
146             return Optional.empty();
147         }
148         // Using filterByNodeType predicate when calling Stream.count() at size() method
149         // will cause a sort of infinite loop where size() seems to be called forever.
150         final String types = definition.getAllowedNodeTypes()
151                 .stream()
152                 .map(type -> String.format("t.[jcr:primaryType] = '%s'", type))
153                 .collect(Collectors.joining(" or "));
154         return Optional.of(String.format(" where (%s)", types));
155     }
156 
157     private Optional<String> createOrderByClause(Query<Node, DataFilter> query) {
158         if (!definition.isSortable()) {
159             return Optional.empty();
160         }
161         StringBuilder sortOrder = new StringBuilder();
162 
163         // first build the default sort options
164         if (CollectionUtils.isNotEmpty(definition.getSortByProperties())) {
165             sortOrder.append(definition.getSortByProperties()
166                     .stream()
167                     .map(o -> String.format("t.[%s] asc", o))
168                     .collect(Collectors.joining(",")));
169         }
170         // build other sort orders possibly set by wrappers, components, etc.
171         sortOrder.append(query.getSortOrders()
172                 .stream()
173                 .map(so -> resolveProperty(so))
174                 .collect(Collectors.joining(",")));
175 
176         return sortOrder.length() == 0 ? Optional.empty() : Optional.of(String.format(" order by %s", sortOrder.toString()));
177     }
178 
179     private String resolveProperty(SortOrder so) {
180        return String.format("%s %s", "jcrName".equals(so.getSorted()) ? "lower(name(t))" :
181                String.format("t.[%s]", so.getSorted()), so.getDirection() == ASCENDING? "asc" : "desc");
182     }
183 }