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