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