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}