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