/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Vladimir Pasquier <vpasquier@nuxeo.com>
*/
package org.nuxeo.ecm.restapi.server.jaxrs;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.automation.core.util.DocumentHelper;
import org.nuxeo.ecm.automation.core.util.Properties;
import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl;
import org.nuxeo.ecm.automation.server.jaxrs.RestOperationException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.SortInfo;
import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
import org.nuxeo.ecm.platform.query.api.PageProvider;
import org.nuxeo.ecm.platform.query.api.PageProviderDefinition;
import org.nuxeo.ecm.platform.query.api.PageProviderService;
import org.nuxeo.ecm.platform.query.api.QuickFilter;
import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider;
import org.nuxeo.ecm.restapi.server.jaxrs.adapters.SearchAdapter;
import org.nuxeo.ecm.webengine.model.WebObject;
import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
import org.nuxeo.runtime.api.Framework;
/**
* @since 6.0 Search endpoint to perform queries on the repository through rest api.
*/
@WebObject(type = "query")
public class QueryObject extends AbstractResource<ResourceTypeImpl> {
public static final String PATH = "query";
public static final String NXQL = "NXQL";
public static final String QUERY = "query";
public static final String PAGE_SIZE = "pageSize";
public static final String CURRENT_PAGE_INDEX = "currentPageIndex";
public static final String MAX_RESULTS = "maxResults";
public static final String SORT_BY = "sortBy";
public static final String SORT_ORDER = "sortOrder";
public static final String ORDERED_PARAMS = "queryParams";
public static final String CURRENT_USERID_PATTERN = "$currentUser";
public static final String CURRENT_REPO_PATTERN = "$currentRepository";
/**
* @since 8.4
*/
public static final String QUICK_FILTERS = "quickFilters";
private static final Log log = LogFactory.getLog(QueryObject.class);
protected EnumMap<QueryParams, String> queryParametersMap;
protected EnumMap<LangParams, String> langPathMap;
protected PageProviderService pageProviderService;
@Override
public void initialize(Object... args) {
pageProviderService = Framework.getLocalService(PageProviderService.class);
// Query Enum Parameters Map
queryParametersMap = new EnumMap<>(QueryParams.class);
queryParametersMap.put(QueryParams.PAGE_SIZE, PAGE_SIZE);
queryParametersMap.put(QueryParams.CURRENT_PAGE_INDEX, CURRENT_PAGE_INDEX);
queryParametersMap.put(QueryParams.MAX_RESULTS, MAX_RESULTS);
queryParametersMap.put(QueryParams.SORT_BY, SORT_BY);
queryParametersMap.put(QueryParams.SORT_ORDER, SORT_ORDER);
queryParametersMap.put(QueryParams.QUERY, QUERY);
queryParametersMap.put(QueryParams.ORDERED_PARAMS, ORDERED_PARAMS);
queryParametersMap.put(QueryParams.QUICK_FILTERS, QUICK_FILTERS);
// Lang Path Enum Map
langPathMap = new EnumMap<>(LangParams.class);
langPathMap.put(LangParams.NXQL, NXQL);
}
@SuppressWarnings("unchecked")
protected DocumentModelList getQuery(UriInfo uriInfo, String langOrProviderName) throws RestOperationException {
// Fetching all parameters
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
// Look if provider name is given
String providerName = null;
if (!langPathMap.containsValue(langOrProviderName)) {
providerName = langOrProviderName;
}
String query = queryParams.getFirst(QUERY);
String pageSize = queryParams.getFirst(PAGE_SIZE);
String currentPageIndex = queryParams.getFirst(CURRENT_PAGE_INDEX);
String maxResults = queryParams.getFirst(MAX_RESULTS);
String sortBy = queryParams.getFirst(SORT_BY);
String sortOrder = queryParams.getFirst(SORT_ORDER);
List<String> orderedParams = queryParams.get(ORDERED_PARAMS);
String quickFilters = queryParams.getFirst(QUICK_FILTERS);
// If no query or provider name has been found
// Execute big select
if (query == null && StringUtils.isBlank(providerName)) {
// provide a defaut query
query = "SELECT * from Document";
}
// Fetching named parameters (other custom query parameters in the
// path)
Properties namedParameters = new Properties();
for (String namedParameterKey : queryParams.keySet()) {
if (!queryParametersMap.containsValue(namedParameterKey)) {
String value = queryParams.getFirst(namedParameterKey);
if (value != null) {
if (value.equals(CURRENT_USERID_PATTERN)) {
value = ctx.getCoreSession().getPrincipal().getName();
} else if (value.equals(CURRENT_REPO_PATTERN)) {
value = ctx.getCoreSession().getRepositoryName();
}
}
namedParameters.put(namedParameterKey, value);
}
}
// Target query page
Long targetPage = null;
if (currentPageIndex != null) {
targetPage = Long.valueOf(currentPageIndex);
}
// Target page size
Long targetPageSize = null;
if (pageSize != null) {
targetPageSize = Long.valueOf(pageSize);
}
// Ordered Parameters
Object[] parameters = null;
if (orderedParams != null && !orderedParams.isEmpty()) {
parameters = orderedParams.toArray(new String[orderedParams.size()]);
// expand specific parameters
for (int idx = 0; idx < parameters.length; idx++) {
String value = (String) parameters[idx];
if (value.equals(CURRENT_USERID_PATTERN)) {
parameters[idx] = ctx.getCoreSession().getPrincipal().getName();
} else if (value.equals(CURRENT_REPO_PATTERN)) {
parameters[idx] = ctx.getCoreSession().getRepositoryName();
}
}
}
Map<String, Serializable> props = new HashMap<String, Serializable>();
props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) ctx.getCoreSession());
DocumentModel searchDocumentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService,
providerName, namedParameters);
// Sort Info Management
List<SortInfo> sortInfoList = null;
if (!StringUtils.isBlank(sortBy)) {
sortInfoList = new ArrayList<>();
String[] sorts = sortBy.split(",");
String[] orders = null;
if (!StringUtils.isBlank(sortOrder)) {
orders = sortOrder.split(",");
}
for (int i = 0; i < sorts.length; i++) {
String sort = sorts[i];
boolean sortAscending = (orders != null && orders.length > i && "asc".equals(orders[i].toLowerCase()));
sortInfoList.add(new SortInfo(sort, sortAscending));
}
}
PaginableDocumentModelListImpl res;
if (query != null) {
PageProviderDefinition ppdefinition = pageProviderService.getPageProviderDefinition(
SearchAdapter.pageProviderName);
ppdefinition.setPattern(query);
if (maxResults != null && !maxResults.isEmpty() && !maxResults.equals("-1")) {
// set the maxResults to avoid slowing down queries
ppdefinition.getProperties().put("maxResults", maxResults);
}
if (StringUtils.isBlank(providerName)) {
providerName = SearchAdapter.pageProviderName;
}
res = new PaginableDocumentModelListImpl(
(PageProvider<DocumentModel>) pageProviderService.getPageProvider(providerName, ppdefinition,
searchDocumentModel, sortInfoList, targetPageSize, targetPage, props, parameters),
null);
} else {
PageProviderDefinition pageProviderDefinition = pageProviderService.getPageProviderDefinition(providerName);
// Quick filters management
List<QuickFilter> quickFilterList = new ArrayList<>();
if (quickFilters != null && !quickFilters.isEmpty()) {
String[] filters = quickFilters.split(",");
List<QuickFilter> ppQuickFilters = pageProviderDefinition.getQuickFilters();
for (String filter : filters) {
for (QuickFilter quickFilter : ppQuickFilters) {
if (quickFilter.getName().equals(filter)) {
quickFilterList.add(quickFilter);
break;
}
}
}
}
res = new PaginableDocumentModelListImpl(
(PageProvider<DocumentModel>) pageProviderService.getPageProvider(providerName, searchDocumentModel,
sortInfoList, targetPageSize, targetPage, props, quickFilterList, parameters),
null);
}
if (res.hasError()) {
RestOperationException err = new RestOperationException(res.getErrorMessage());
err.setStatus(HttpServletResponse.SC_BAD_REQUEST);
throw err;
}
return res;
}
protected DocumentModel getSearchDocumentModel(CoreSession session, PageProviderService pps, String providerName,
Properties namedParameters) {
// generate search document model if type specified on the definition
DocumentModel searchDocumentModel = null;
if (!StringUtils.isBlank(providerName)) {
PageProviderDefinition pageProviderDefinition = pps.getPageProviderDefinition(providerName);
if (pageProviderDefinition != null) {
String searchDocType = pageProviderDefinition.getSearchDocumentType();
if (searchDocType != null) {
searchDocumentModel = session.createDocumentModel(searchDocType);
} else if (pageProviderDefinition.getWhereClause() != null) {
// avoid later error on null search doc, in case where clause is only referring to named parameters
// (and no namedParameters are given)
searchDocumentModel = new SimpleDocumentModel();
}
} else {
log.error("No page provider definition found for " + providerName);
}
}
if (namedParameters != null && !namedParameters.isEmpty()) {
// fall back on simple document if no type defined on page provider
if (searchDocumentModel == null) {
searchDocumentModel = new SimpleDocumentModel();
}
for (Map.Entry<String, String> entry : namedParameters.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
try {
DocumentHelper.setProperty(session, searchDocumentModel, key, value, true);
} catch (PropertyNotFoundException | IOException e) {
// assume this is a "pure" named parameter, not part of the search doc schema
continue;
}
}
searchDocumentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParameters);
}
return searchDocumentModel;
}
/**
* Perform query on the repository. By default in NXQL.
*
* @param uriInfo Query parameters
* @return Document Listing
*/
@GET
public Object doQuery(@Context UriInfo uriInfo) throws RestOperationException {
return getQuery(uriInfo, NXQL);
}
/**
* Perform query on the repository in NXQL or specific pageprovider name
*
* @param uriInfo Query parameters
* @param langOrProviderName NXQL or specific provider name
* @return Document Listing
*/
@GET
@Path("{langOrProviderName}")
public Object doSpecificQuery(@Context UriInfo uriInfo, @PathParam("langOrProviderName") String langOrProviderName)
throws RestOperationException {
return getQuery(uriInfo, langOrProviderName);
}
public enum QueryParams {
PAGE_SIZE, CURRENT_PAGE_INDEX, MAX_RESULTS, SORT_BY, SORT_ORDER, ORDERED_PARAMS, QUERY, QUICK_FILTERS
}
public enum LangParams {
NXQL
}
}