001/*
002 * (C) Copyright 2014-2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Vladimir Pasquier <vpasquier@nuxeo.com>
018 */
019package org.nuxeo.ecm.restapi.server.jaxrs;
020
021import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
022
023import java.io.Serializable;
024import java.util.ArrayList;
025import java.util.EnumMap;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import javax.ws.rs.GET;
031import javax.ws.rs.Path;
032import javax.ws.rs.PathParam;
033import javax.ws.rs.core.Context;
034import javax.ws.rs.core.MultivaluedMap;
035import javax.ws.rs.core.UriInfo;
036
037import org.apache.commons.lang3.StringUtils;
038import org.nuxeo.ecm.automation.core.util.PageProviderHelper;
039import org.nuxeo.ecm.automation.core.util.Properties;
040import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl;
041import org.nuxeo.ecm.core.api.CoreSession;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.DocumentModelList;
044import org.nuxeo.ecm.core.api.NuxeoException;
045import org.nuxeo.ecm.core.api.SortInfo;
046import org.nuxeo.ecm.platform.query.api.PageProvider;
047import org.nuxeo.ecm.platform.query.api.PageProviderDefinition;
048import org.nuxeo.ecm.platform.query.api.PageProviderService;
049import org.nuxeo.ecm.platform.query.api.QuickFilter;
050import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider;
051import org.nuxeo.ecm.restapi.server.jaxrs.adapters.SearchAdapter;
052import org.nuxeo.ecm.webengine.model.WebObject;
053import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
054import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
055import org.nuxeo.runtime.api.Framework;
056
057/**
058 * @since 6.0 Search endpoint to perform queries on the repository through rest api.
059 */
060@WebObject(type = "query")
061public class QueryObject extends AbstractResource<ResourceTypeImpl> {
062
063    public static final String PATH = "query";
064
065    public static final String NXQL = "NXQL";
066
067    public static final String QUERY = "query";
068
069    public static final String PAGE_SIZE = "pageSize";
070
071    public static final String CURRENT_PAGE_INDEX = "currentPageIndex";
072
073    public static final String MAX_RESULTS = "maxResults";
074
075    public static final String SORT_BY = "sortBy";
076
077    public static final String SORT_ORDER = "sortOrder";
078
079    public static final String ORDERED_PARAMS = "queryParams";
080
081    public static final String CURRENT_USERID_PATTERN = "$currentUser";
082
083    public static final String CURRENT_REPO_PATTERN = "$currentRepository";
084
085    /**
086     * @since 8.4
087     */
088    public static final String QUICK_FILTERS = "quickFilters";
089
090    protected EnumMap<QueryParams, String> queryParametersMap;
091
092    protected EnumMap<LangParams, String> langPathMap;
093
094    protected PageProviderService pageProviderService;
095
096    @Override
097    public void initialize(Object... args) {
098        pageProviderService = Framework.getService(PageProviderService.class);
099        // Query Enum Parameters Map
100        queryParametersMap = new EnumMap<>(QueryParams.class);
101        queryParametersMap.put(QueryParams.PAGE_SIZE, PAGE_SIZE);
102        queryParametersMap.put(QueryParams.CURRENT_PAGE_INDEX, CURRENT_PAGE_INDEX);
103        queryParametersMap.put(QueryParams.MAX_RESULTS, MAX_RESULTS);
104        queryParametersMap.put(QueryParams.SORT_BY, SORT_BY);
105        queryParametersMap.put(QueryParams.SORT_ORDER, SORT_ORDER);
106        queryParametersMap.put(QueryParams.QUERY, QUERY);
107        queryParametersMap.put(QueryParams.ORDERED_PARAMS, ORDERED_PARAMS);
108        queryParametersMap.put(QueryParams.QUICK_FILTERS, QUICK_FILTERS);
109        // Lang Path Enum Map
110        langPathMap = new EnumMap<>(LangParams.class);
111        langPathMap.put(LangParams.NXQL, NXQL);
112    }
113
114    @SuppressWarnings("unchecked")
115    protected DocumentModelList getQuery(UriInfo uriInfo, String langOrProviderName) {
116        // Fetching all parameters
117        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
118        // Look if provider name is given
119        String providerName = null;
120        if (!langPathMap.containsValue(langOrProviderName)) {
121            providerName = langOrProviderName;
122        }
123        String query = queryParams.getFirst(QUERY);
124        String pageSize = queryParams.getFirst(PAGE_SIZE);
125        String currentPageIndex = queryParams.getFirst(CURRENT_PAGE_INDEX);
126        String maxResults = queryParams.getFirst(MAX_RESULTS);
127        String sortBy = queryParams.getFirst(SORT_BY);
128        String sortOrder = queryParams.getFirst(SORT_ORDER);
129        List<String> orderedParams = queryParams.get(ORDERED_PARAMS);
130        String quickFilters = queryParams.getFirst(QUICK_FILTERS);
131
132        // If no query or provider name has been found
133        // Execute big select
134        if (query == null && StringUtils.isBlank(providerName)) {
135            // provide a defaut query
136            query = "SELECT * from Document";
137        }
138
139        // Fetching named parameters (other custom query parameters in the
140        // path)
141        Properties namedParameters = new Properties();
142        for (String namedParameterKey : queryParams.keySet()) {
143            if (!queryParametersMap.containsValue(namedParameterKey)) {
144                String value = queryParams.getFirst(namedParameterKey);
145                if (value != null) {
146                    if (value.equals(CURRENT_USERID_PATTERN)) {
147                        value = ctx.getCoreSession().getPrincipal().getName();
148                    } else if (value.equals(CURRENT_REPO_PATTERN)) {
149                        value = ctx.getCoreSession().getRepositoryName();
150                    }
151                }
152                namedParameters.put(namedParameterKey, value);
153            }
154        }
155
156        // Target query page
157        Long targetPage = null;
158        if (currentPageIndex != null) {
159            targetPage = Long.valueOf(currentPageIndex);
160        }
161
162        // Target page size
163        Long targetPageSize = null;
164        if (pageSize != null) {
165            targetPageSize = Long.valueOf(pageSize);
166        }
167
168        // Ordered Parameters
169        Object[] parameters = null;
170        if (orderedParams != null && !orderedParams.isEmpty()) {
171            parameters = orderedParams.toArray(new String[0]);
172            // expand specific parameters
173            for (int idx = 0; idx < parameters.length; idx++) {
174                String value = (String) parameters[idx];
175                if (value.equals(CURRENT_USERID_PATTERN)) {
176                    parameters[idx] = ctx.getCoreSession().getPrincipal().getName();
177                } else if (value.equals(CURRENT_REPO_PATTERN)) {
178                    parameters[idx] = ctx.getCoreSession().getRepositoryName();
179                }
180            }
181        }
182
183        Map<String, Serializable> props = new HashMap<>();
184        props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) ctx.getCoreSession());
185
186        DocumentModel searchDocumentModel = PageProviderHelper.getSearchDocumentModel(ctx.getCoreSession(),
187                pageProviderService, providerName, namedParameters);
188
189        // Sort Info Management
190        List<SortInfo> sortInfoList = null;
191        if (!StringUtils.isBlank(sortBy)) {
192            sortInfoList = new ArrayList<>();
193            String[] sorts = sortBy.split(",");
194            String[] orders = null;
195            if (!StringUtils.isBlank(sortOrder)) {
196                orders = sortOrder.split(",");
197            }
198            for (int i = 0; i < sorts.length; i++) {
199                String sort = sorts[i];
200                boolean sortAscending = (orders != null && orders.length > i && "asc".equalsIgnoreCase(orders[i]));
201                sortInfoList.add(new SortInfo(sort, sortAscending));
202            }
203        }
204
205        PaginableDocumentModelListImpl res;
206        if (query != null) {
207            PageProviderDefinition ppdefinition = pageProviderService.getPageProviderDefinition(
208                    SearchAdapter.pageProviderName);
209            ppdefinition.setPattern(query);
210            if (maxResults != null && !maxResults.isEmpty() && !maxResults.equals("-1")) {
211                // set the maxResults to avoid slowing down queries
212                ppdefinition.getProperties().put("maxResults", maxResults);
213            }
214            if (StringUtils.isBlank(providerName)) {
215                providerName = SearchAdapter.pageProviderName;
216            }
217
218            res = new PaginableDocumentModelListImpl(
219                    (PageProvider<DocumentModel>) pageProviderService.getPageProvider(providerName, ppdefinition,
220                            searchDocumentModel, sortInfoList, targetPageSize, targetPage, props, parameters),
221                    null);
222        } else {
223            PageProviderDefinition pageProviderDefinition = pageProviderService.getPageProviderDefinition(providerName);
224            // Quick filters management
225            List<QuickFilter> quickFilterList = new ArrayList<>();
226            if (quickFilters != null && !quickFilters.isEmpty()) {
227                String[] filters = quickFilters.split(",");
228                List<QuickFilter> ppQuickFilters = pageProviderDefinition.getQuickFilters();
229                for (String filter : filters) {
230                    for (QuickFilter quickFilter : ppQuickFilters) {
231                        if (quickFilter.getName().equals(filter)) {
232                            quickFilterList.add(quickFilter);
233                            break;
234                        }
235                    }
236                }
237            }
238            res = new PaginableDocumentModelListImpl(
239                    (PageProvider<DocumentModel>) pageProviderService.getPageProvider(providerName, searchDocumentModel,
240                            sortInfoList, targetPageSize, targetPage, props, quickFilterList, parameters),
241                    null);
242        }
243        if (res.hasError()) {
244            throw new NuxeoException(res.getErrorMessage(), SC_BAD_REQUEST);
245        }
246        return res;
247    }
248
249    /**
250     * @deprecated since 11.1, use
251     *             {@link PageProviderHelper#getSearchDocumentModel(CoreSession, PageProviderService, String, Map)}
252     *             instead
253     */
254    @Deprecated(since = "11.1")
255    protected DocumentModel getSearchDocumentModel(CoreSession session, PageProviderService pps, String providerName,
256            Properties namedParameters) {
257        return PageProviderHelper.getSearchDocumentModel(session, pps, providerName, namedParameters);
258    }
259
260    /**
261     * Perform query on the repository. By default in NXQL.
262     *
263     * @param uriInfo Query parameters
264     * @return Document Listing
265     */
266    @GET
267    public Object doQuery(@Context UriInfo uriInfo) {
268        return getQuery(uriInfo, NXQL);
269    }
270
271    /**
272     * Perform query on the repository in NXQL or specific pageprovider name
273     *
274     * @param uriInfo Query parameters
275     * @param langOrProviderName NXQL or specific provider name
276     * @return Document Listing
277     */
278    @GET
279    @Path("{langOrProviderName}")
280    public Object doSpecificQuery(@Context UriInfo uriInfo,
281            @PathParam("langOrProviderName") String langOrProviderName) {
282        return getQuery(uriInfo, langOrProviderName);
283    }
284
285    public enum QueryParams {
286        PAGE_SIZE, CURRENT_PAGE_INDEX, MAX_RESULTS, SORT_BY, SORT_ORDER, ORDERED_PARAMS, QUERY, QUICK_FILTERS
287    }
288
289    public enum LangParams {
290        NXQL
291    }
292
293}