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 String value = queryParams.getFirst(namedParameterKey); 143 if (value != null) { 144 if (value.equals(CURRENT_USERID_PATTERN)) { 145 value = ctx.getCoreSession().getPrincipal().getName(); 146 } else if (value.equals(CURRENT_REPO_PATTERN)) { 147 value = ctx.getCoreSession().getRepositoryName(); 148 } 149 } 150 namedParameters.put(namedParameterKey, value); 151 } 152 } 153 154 // Target query page 155 Long targetPage = null; 156 if (currentPageIndex != null) { 157 targetPage = Long.valueOf(currentPageIndex); 158 } 159 160 // Target page size 161 Long targetPageSize = null; 162 if (pageSize != null) { 163 targetPageSize = Long.valueOf(pageSize); 164 } 165 166 // Ordered Parameters 167 Object[] parameters = null; 168 if (orderedParams != null && !orderedParams.isEmpty()) { 169 parameters = orderedParams.toArray(new String[orderedParams.size()]); 170 // expand specific parameters 171 for (int idx = 0; idx < parameters.length; idx++) { 172 String value = (String) parameters[idx]; 173 if (value.equals(CURRENT_USERID_PATTERN)) { 174 parameters[idx] = ctx.getCoreSession().getPrincipal().getName(); 175 } else if (value.equals(CURRENT_REPO_PATTERN)) { 176 parameters[idx] = ctx.getCoreSession().getRepositoryName(); 177 } 178 } 179 } 180 181 Map<String, Serializable> props = new HashMap<String, Serializable>(); 182 props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) ctx.getCoreSession()); 183 184 DocumentModel searchDocumentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService, 185 providerName, namedParameters); 186 187 // Sort Info Management 188 List<SortInfo> sortInfoList = new ArrayList<>(); 189 if (!StringUtils.isBlank(sortBy)) { 190 String[] sorts = sortBy.split(","); 191 String[] orders = null; 192 if (!StringUtils.isBlank(sortOrder)) { 193 orders = sortOrder.split(","); 194 } 195 for (int i = 0; i < sorts.length; i++) { 196 String sort = sorts[i]; 197 boolean sortAscending = (orders != null && orders.length > i && "asc".equals(orders[i].toLowerCase())); 198 sortInfoList.add(new SortInfo(sort, sortAscending)); 199 } 200 } 201 202 PaginableDocumentModelListImpl res; 203 if (query != null) { 204 PageProviderDefinition ppdefinition = pageProviderService.getPageProviderDefinition(SearchAdapter.pageProviderName); 205 ppdefinition.setPattern(query); 206 if (maxResults != null && !maxResults.isEmpty() && !maxResults.equals("-1")) { 207 // set the maxResults to avoid slowing down queries 208 ppdefinition.getProperties().put("maxResults", maxResults); 209 } 210 if(StringUtils.isBlank(providerName)){ 211 providerName = SearchAdapter.pageProviderName; 212 } 213 res = new PaginableDocumentModelListImpl((PageProvider<DocumentModel>) pageProviderService.getPageProvider( 214 providerName, ppdefinition, searchDocumentModel, sortInfoList, targetPageSize, targetPage, 215 props, parameters), null); 216 } else { 217 res = new PaginableDocumentModelListImpl((PageProvider<DocumentModel>) pageProviderService.getPageProvider( 218 providerName, searchDocumentModel, sortInfoList, targetPageSize, targetPage, props, parameters), 219 null); 220 } 221 if (res.hasError()) { 222 RestOperationException err = new RestOperationException(res.getErrorMessage()); 223 err.setStatus(HttpServletResponse.SC_BAD_REQUEST); 224 throw err; 225 } 226 return res; 227 } 228 229 protected DocumentModel getSearchDocumentModel(CoreSession session, PageProviderService pps, String providerName, 230 Properties namedParameters) { 231 // generate search document model if type specified on the definition 232 DocumentModel searchDocumentModel = null; 233 if (!StringUtils.isBlank(providerName)) { 234 PageProviderDefinition pageProviderDefinition = pps.getPageProviderDefinition(providerName); 235 if (pageProviderDefinition != null) { 236 String searchDocType = pageProviderDefinition.getSearchDocumentType(); 237 if (searchDocType != null) { 238 searchDocumentModel = session.createDocumentModel(searchDocType); 239 } else if (pageProviderDefinition.getWhereClause() != null) { 240 // avoid later error on null search doc, in case where clause is only referring to named parameters 241 // (and no namedParameters are given) 242 searchDocumentModel = new SimpleDocumentModel(); 243 } 244 } else { 245 log.error("No page provider definition found for " + providerName); 246 } 247 } 248 249 if (namedParameters != null && !namedParameters.isEmpty()) { 250 // fall back on simple document if no type defined on page provider 251 if (searchDocumentModel == null) { 252 searchDocumentModel = new SimpleDocumentModel(); 253 } 254 for (Map.Entry<String, String> entry : namedParameters.entrySet()) { 255 String key = entry.getKey(); 256 String value = entry.getValue(); 257 try { 258 DocumentHelper.setProperty(session, searchDocumentModel, key, value, true); 259 } catch (PropertyNotFoundException | IOException e) { 260 // assume this is a "pure" named parameter, not part of the search doc schema 261 continue; 262 } 263 } 264 searchDocumentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParameters); 265 } 266 return searchDocumentModel; 267 } 268 269 /** 270 * Perform query on the repository. By default in NXQL. 271 * 272 * @param uriInfo Query parameters 273 * @return Document Listing 274 */ 275 @GET 276 public Object doQuery(@Context UriInfo uriInfo) throws RestOperationException { 277 return getQuery(uriInfo, NXQL); 278 } 279 280 /** 281 * Perform query on the repository in NXQL or specific pageprovider name 282 * 283 * @param uriInfo Query parameters 284 * @param langOrProviderName NXQL or specific provider name 285 * @return Document Listing 286 */ 287 @GET 288 @Path("{langOrProviderName}") 289 public Object doSpecificQuery(@Context UriInfo uriInfo, @PathParam("langOrProviderName") String langOrProviderName) 290 throws RestOperationException { 291 return getQuery(uriInfo, langOrProviderName); 292 } 293 294 public enum QueryParams { 295 PAGE_SIZE, CURRENT_PAGE_INDEX, MAX_RESULTS, SORT_BY, SORT_ORDER, ORDERED_PARAMS, QUERY 296 } 297 298 public enum LangParams { 299 NXQL 300 } 301 302}