001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Vladimir Pasquier <vpasquier@nuxeo.com>
016 */
017package org.nuxeo.ecm.restapi.server.jaxrs;
018
019import java.io.IOException;
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.EnumMap;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import javax.servlet.http.HttpServletResponse;
028import javax.ws.rs.GET;
029import javax.ws.rs.Path;
030import javax.ws.rs.PathParam;
031import javax.ws.rs.core.Context;
032import javax.ws.rs.core.MultivaluedMap;
033import javax.ws.rs.core.UriInfo;
034
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.ecm.automation.core.util.DocumentHelper;
039import org.nuxeo.ecm.automation.core.util.Properties;
040import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl;
041import org.nuxeo.ecm.automation.server.jaxrs.RestOperationException;
042import org.nuxeo.ecm.core.api.CoreSession;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.DocumentModelList;
045import org.nuxeo.ecm.core.api.SortInfo;
046import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
047import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
048import org.nuxeo.ecm.platform.query.api.PageProvider;
049import org.nuxeo.ecm.platform.query.api.PageProviderDefinition;
050import org.nuxeo.ecm.platform.query.api.PageProviderService;
051import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider;
052import org.nuxeo.ecm.restapi.server.jaxrs.adapters.SearchAdapter;
053import org.nuxeo.ecm.webengine.model.WebObject;
054import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
055import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
056import org.nuxeo.runtime.api.Framework;
057
058/**
059 * @since 6.0 Search endpoint to perform queries on the repository through rest api.
060 */
061@WebObject(type = "query")
062public class QueryObject extends AbstractResource<ResourceTypeImpl> {
063
064    public static final String PATH = "query";
065
066    public static final String NXQL = "NXQL";
067
068    public static final String QUERY = "query";
069
070    public static final String PAGE_SIZE = "pageSize";
071
072    public static final String CURRENT_PAGE_INDEX = "currentPageIndex";
073
074    public static final String MAX_RESULTS = "maxResults";
075
076    public static final String SORT_BY = "sortBy";
077
078    public static final String SORT_ORDER = "sortOrder";
079
080    public static final String ORDERED_PARAMS = "queryParams";
081
082    public static final String CURRENT_USERID_PATTERN = "$currentUser";
083
084    public static final String CURRENT_REPO_PATTERN = "$currentRepository";
085
086    private static final Log log = LogFactory.getLog(QueryObject.class);
087
088    protected EnumMap<QueryParams, String> queryParametersMap;
089
090    protected EnumMap<LangParams, String> langPathMap;
091
092    protected PageProviderService pageProviderService;
093
094    @Override
095    public void initialize(Object... args) {
096        pageProviderService = Framework.getLocalService(PageProviderService.class);
097        // Query Enum Parameters Map
098        queryParametersMap = new EnumMap<>(QueryParams.class);
099        queryParametersMap.put(QueryParams.PAGE_SIZE, PAGE_SIZE);
100        queryParametersMap.put(QueryParams.CURRENT_PAGE_INDEX, CURRENT_PAGE_INDEX);
101        queryParametersMap.put(QueryParams.MAX_RESULTS, MAX_RESULTS);
102        queryParametersMap.put(QueryParams.SORT_BY, SORT_BY);
103        queryParametersMap.put(QueryParams.SORT_ORDER, SORT_ORDER);
104        queryParametersMap.put(QueryParams.QUERY, QUERY);
105        queryParametersMap.put(QueryParams.ORDERED_PARAMS, ORDERED_PARAMS);
106        // Lang Path Enum Map
107        langPathMap = new EnumMap<>(LangParams.class);
108        langPathMap.put(LangParams.NXQL, NXQL);
109    }
110
111    @SuppressWarnings("unchecked")
112    protected DocumentModelList getQuery(UriInfo uriInfo, String langOrProviderName) throws RestOperationException {
113        // Fetching all parameters
114        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
115        // Look if provider name is given
116        String providerName = null;
117        if (!langPathMap.containsValue(langOrProviderName)) {
118            providerName = langOrProviderName;
119        }
120        String query = queryParams.getFirst(QUERY);
121        String pageSize = queryParams.getFirst(PAGE_SIZE);
122        String currentPageIndex = queryParams.getFirst(CURRENT_PAGE_INDEX);
123        String maxResults = queryParams.getFirst(MAX_RESULTS);
124        String sortBy = queryParams.getFirst(SORT_BY);
125        String sortOrder = queryParams.getFirst(SORT_ORDER);
126        List<String> orderedParams = queryParams.get(ORDERED_PARAMS);
127
128        // If no query or provider name has been found
129        // Execute big select
130        if (query == null && StringUtils.isBlank(providerName)) {
131            // provide a defaut query
132            query = "SELECT * from Document";
133        }
134
135        // Fetching named parameters (other custom query parameters in the
136        // path)
137        Properties namedParameters = new Properties();
138        for (String namedParameterKey : queryParams.keySet()) {
139            if (!queryParametersMap.containsValue(namedParameterKey)) {
140                namedParameters.put(namedParameterKey, queryParams.getFirst(namedParameterKey));
141            }
142        }
143
144        // Target query page
145        Long targetPage = null;
146        if (currentPageIndex != null) {
147            targetPage = Long.valueOf(currentPageIndex);
148        }
149
150        // Target page size
151        Long targetPageSize = null;
152        if (pageSize != null) {
153            targetPageSize = Long.valueOf(pageSize);
154        }
155
156        // Ordered Parameters
157        Object[] parameters = null;
158        if (orderedParams != null && !orderedParams.isEmpty()) {
159            parameters = orderedParams.toArray(new String[orderedParams.size()]);
160            // expand specific parameters
161            for (int idx = 0; idx < parameters.length; idx++) {
162                String value = (String) parameters[idx];
163                if (value.equals(CURRENT_USERID_PATTERN)) {
164                    parameters[idx] = ctx.getCoreSession().getPrincipal().getName();
165                } else if (value.equals(CURRENT_REPO_PATTERN)) {
166                    parameters[idx] = ctx.getCoreSession().getRepositoryName();
167                }
168            }
169        }
170
171        Map<String, Serializable> props = new HashMap<String, Serializable>();
172        props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) ctx.getCoreSession());
173
174        DocumentModel searchDocumentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService,
175                providerName, namedParameters);
176
177        // Sort Info Management
178        List<SortInfo> sortInfoList = new ArrayList<>();
179        if (!StringUtils.isBlank(sortBy)) {
180            String[] sorts = sortBy.split(",");
181            String[] orders = null;
182            if (!StringUtils.isBlank(sortOrder)) {
183                orders = sortOrder.split(",");
184            }
185            for (int i = 0; i < sorts.length; i++) {
186                String sort = sorts[i];
187                boolean sortAscending = (orders != null && orders.length > i && "asc".equals(orders[i].toLowerCase()));
188                sortInfoList.add(new SortInfo(sort, sortAscending));
189            }
190        }
191
192        PaginableDocumentModelListImpl res;
193        if (query != null) {
194            PageProviderDefinition ppdefinition = pageProviderService.getPageProviderDefinition(SearchAdapter.pageProviderName);
195            ppdefinition.setPattern(query);
196            if (maxResults != null && !maxResults.isEmpty() && !maxResults.equals("-1")) {
197                // set the maxResults to avoid slowing down queries
198                ppdefinition.getProperties().put("maxResults", maxResults);
199            }
200            if(StringUtils.isBlank(providerName)){
201                providerName = SearchAdapter.pageProviderName;
202            }
203            res = new PaginableDocumentModelListImpl((PageProvider<DocumentModel>) pageProviderService.getPageProvider(
204                    providerName, ppdefinition, searchDocumentModel, sortInfoList, targetPageSize, targetPage,
205                    props, parameters), null);
206        } else {
207            res = new PaginableDocumentModelListImpl((PageProvider<DocumentModel>) pageProviderService.getPageProvider(
208                    providerName, searchDocumentModel, sortInfoList, targetPageSize, targetPage, props, parameters),
209                    null);
210        }
211        if (res.hasError()) {
212            RestOperationException err = new RestOperationException(res.getErrorMessage());
213            err.setStatus(HttpServletResponse.SC_BAD_REQUEST);
214            throw err;
215        }
216        return res;
217    }
218
219    protected DocumentModel getSearchDocumentModel(CoreSession session, PageProviderService pps, String providerName,
220            Properties namedParameters) {
221        // generate search document model if type specified on the definition
222        DocumentModel searchDocumentModel = null;
223        if (!StringUtils.isBlank(providerName)) {
224            PageProviderDefinition pageProviderDefinition = pps.getPageProviderDefinition(providerName);
225            if (pageProviderDefinition != null) {
226                String searchDocType = pageProviderDefinition.getSearchDocumentType();
227                if (searchDocType != null) {
228                    searchDocumentModel = session.createDocumentModel(searchDocType);
229                } else if (pageProviderDefinition.getWhereClause() != null) {
230                    // avoid later error on null search doc, in case where clause is only referring to named parameters
231                    // (and no namedParameters are given)
232                    searchDocumentModel = new SimpleDocumentModel();
233                }
234            } else {
235                log.error("No page provider definition found for " + providerName);
236            }
237        }
238
239        if (namedParameters != null && !namedParameters.isEmpty()) {
240            // fall back on simple document if no type defined on page provider
241            if (searchDocumentModel == null) {
242                searchDocumentModel = new SimpleDocumentModel();
243            }
244            for (Map.Entry<String, String> entry : namedParameters.entrySet()) {
245                String key = entry.getKey();
246                String value = entry.getValue();
247                try {
248                    DocumentHelper.setProperty(session, searchDocumentModel, key, value, true);
249                } catch (PropertyNotFoundException | IOException e) {
250                    // assume this is a "pure" named parameter, not part of the search doc schema
251                    continue;
252                }
253            }
254            searchDocumentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParameters);
255        }
256        return searchDocumentModel;
257    }
258
259    /**
260     * Perform query on the repository. By default in NXQL.
261     *
262     * @param uriInfo Query parameters
263     * @return Document Listing
264     */
265    @GET
266    public Object doQuery(@Context UriInfo uriInfo) throws RestOperationException {
267        return getQuery(uriInfo, NXQL);
268    }
269
270    /**
271     * Perform query on the repository in NXQL or specific pageprovider name
272     *
273     * @param uriInfo Query parameters
274     * @param langOrProviderName NXQL or specific provider name
275     * @return Document Listing
276     */
277    @GET
278    @Path("{langOrProviderName}")
279    public Object doSpecificQuery(@Context UriInfo uriInfo, @PathParam("langOrProviderName") String langOrProviderName)
280            throws RestOperationException {
281        return getQuery(uriInfo, langOrProviderName);
282    }
283
284    public enum QueryParams {
285        PAGE_SIZE, CURRENT_PAGE_INDEX, MAX_RESULTS, SORT_BY, SORT_ORDER, ORDERED_PARAMS, QUERY
286    }
287
288    public enum LangParams {
289        NXQL
290    }
291
292}