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