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