001/* 002 * (C) Copyright 2006-2011 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 * Thierry Delprat 018 * Marwane Kalam-Alami 019 */ 020package org.nuxeo.ecm.automation.core.operations.services; 021 022import java.io.IOException; 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import javax.el.ELContext; 031import javax.el.ValueExpression; 032 033import org.apache.commons.lang.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.automation.OperationContext; 037import org.nuxeo.ecm.automation.OperationException; 038import org.nuxeo.ecm.automation.core.Constants; 039import org.nuxeo.ecm.automation.core.annotations.Context; 040import org.nuxeo.ecm.automation.core.annotations.Operation; 041import org.nuxeo.ecm.automation.core.annotations.OperationMethod; 042import org.nuxeo.ecm.automation.core.annotations.Param; 043import org.nuxeo.ecm.automation.core.util.DocumentHelper; 044import org.nuxeo.ecm.automation.core.util.Properties; 045import org.nuxeo.ecm.automation.core.util.StringList; 046import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl; 047import org.nuxeo.ecm.core.api.CoreSession; 048import org.nuxeo.ecm.core.api.DocumentModel; 049import org.nuxeo.ecm.core.api.SortInfo; 050import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel; 051import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 052import org.nuxeo.ecm.core.query.sql.NXQL; 053import org.nuxeo.ecm.platform.actions.ActionContext; 054import org.nuxeo.ecm.platform.actions.ELActionContext; 055import org.nuxeo.ecm.platform.el.ELService; 056import org.nuxeo.ecm.platform.query.api.PageProvider; 057import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 058import org.nuxeo.ecm.platform.query.api.PageProviderService; 059import org.nuxeo.ecm.platform.query.api.QuickFilter; 060import org.nuxeo.ecm.platform.query.core.CoreQueryPageProviderDescriptor; 061import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider; 062import org.nuxeo.runtime.api.Framework; 063 064/** 065 * Operation to execute a query or a named provider with support for Pagination. 066 * 067 * @author Tiry (tdelprat@nuxeo.com) 068 * @since 5.4.2 069 */ 070@Operation(id = DocumentPageProviderOperation.ID, category = Constants.CAT_FETCH, label = "PageProvider", description = "Perform " 071 + "a query or a named provider query on the repository. Result is " 072 + "paginated. The query result will become the input for the next " 073 + "operation. If no query or provider name is given, a query returning " 074 + "all the documents that the user has access to will be executed.", aliases = { "Document.PageProvider" }) 075public class DocumentPageProviderOperation { 076 077 public static final String ID = "Repository.PageProvider"; 078 079 public static final String CURRENT_USERID_PATTERN = "$currentUser"; 080 081 public static final String CURRENT_REPO_PATTERN = "$currentRepository"; 082 083 private static final String SORT_PARAMETER_SEPARATOR = " "; 084 085 public static final String ASC = "ASC"; 086 087 public static final String DESC = "DESC"; 088 089 private static final Log log = LogFactory.getLog(DocumentPageProviderOperation.class); 090 091 @Context 092 protected OperationContext context; 093 094 @Context 095 protected CoreSession session; 096 097 @Context 098 protected PageProviderService ppService; 099 100 @Param(name = "providerName", required = false) 101 protected String providerName; 102 103 /** 104 * @deprecated since 6.0 use instead {@link org.nuxeo.ecm.automation .core.operations.services.query.DocumentQuery}. 105 */ 106 @Deprecated 107 @Param(name = "query", required = false) 108 protected String query; 109 110 @Param(name = "language", required = false, widget = Constants.W_OPTION, values = { NXQL.NXQL }) 111 protected String lang = NXQL.NXQL; 112 113 /** @deprecated since 6.0 use currentPageIndex instead. */ 114 @Param(name = "page", required = false) 115 @Deprecated 116 protected Integer page; 117 118 @Param(name = "currentPageIndex", required = false) 119 protected Integer currentPageIndex; 120 121 @Param(name = "pageSize", required = false) 122 protected Integer pageSize; 123 124 /** 125 * @deprecated since 6.0 use instead {@link #sortBy and @link #sortOrder}. 126 */ 127 @Deprecated 128 @Param(name = "sortInfo", required = false) 129 protected StringList sortInfoAsStringList; 130 131 @Param(name = "queryParams", alias = "searchTerm", required = false) 132 protected StringList strParameters; 133 134 @Param(name = "documentLinkBuilder", required = false) 135 protected String documentLinkBuilder; 136 137 /** 138 * @since 5.7 139 */ 140 @Param(name = "maxResults", required = false) 141 protected String maxResults = "100"; 142 143 /** 144 * @since 6.0 145 */ 146 @Param(name = PageProviderService.NAMED_PARAMETERS, required = false, description = "Named parameters to pass to the page provider to " 147 + "fill in query variables.") 148 protected Properties namedParameters; 149 150 /** 151 * @since 6.0 152 */ 153 @Param(name = "sortBy", required = false, description = "Sort by " + "properties (separated by comma)") 154 protected String sortBy; 155 156 /** 157 * @since 7.10 158 */ 159 @Param(name = "quotePatternParameters", required = false, description = "Quote query parameters if the query is specified") 160 protected boolean quotePatternParameters = true; 161 162 /** 163 * @since 7.10 164 */ 165 @Param(name = "escapePatternParameters", required = false, description = "Escape query parameters if the query is specified") 166 protected boolean escapePatternParameters = true; 167 168 /** 169 * @since 6.0 170 */ 171 @Param(name = "sortOrder", required = false, description = "Sort order, " 172 + "ASC or DESC", widget = Constants.W_OPTION, values = { ASC, DESC }) 173 protected String sortOrder; 174 175 /** 176 * @since 8.4 177 */ 178 @Param(name = "quickFilters", required = false, description = "Quick filter " + "properties (separated by comma)") 179 protected String quickFilters; 180 181 /** 182 * @since 9.1 183 */ 184 @Param(name = "highlights", required = false, description = "Highlight properties (separated by comma)") 185 protected String highlight; 186 187 @SuppressWarnings("unchecked") 188 @OperationMethod 189 public PaginableDocumentModelListImpl run() throws OperationException { 190 List<SortInfo> sortInfos = null; 191 if (sortInfoAsStringList != null) { 192 // BBB 193 sortInfos = new ArrayList<SortInfo>(); 194 for (String sortInfoDesc : sortInfoAsStringList) { 195 SortInfo sortInfo; 196 if (sortInfoDesc.contains(SORT_PARAMETER_SEPARATOR)) { 197 String[] parts = sortInfoDesc.split(SORT_PARAMETER_SEPARATOR); 198 sortInfo = new SortInfo(parts[0], Boolean.parseBoolean(parts[1])); 199 } else { 200 sortInfo = new SortInfo(sortInfoDesc, true); 201 } 202 sortInfos.add(sortInfo); 203 } 204 } else { 205 // Sort Info Management 206 if (!StringUtils.isBlank(sortBy)) { 207 sortInfos = new ArrayList<>(); 208 String[] sorts = sortBy.split(","); 209 String[] orders = null; 210 if (!StringUtils.isBlank(sortOrder)) { 211 orders = sortOrder.split(","); 212 } 213 for (int i = 0; i < sorts.length; i++) { 214 String sort = sorts[i]; 215 boolean sortAscending = (orders != null && orders.length > i 216 && "asc".equals(orders[i].toLowerCase())); 217 sortInfos.add(new SortInfo(sort, sortAscending)); 218 } 219 } 220 } 221 222 Object[] parameters = null; 223 224 if (strParameters != null && !strParameters.isEmpty()) { 225 parameters = strParameters.toArray(new String[strParameters.size()]); 226 // expand specific parameters 227 for (int idx = 0; idx < parameters.length; idx++) { 228 String value = (String) parameters[idx]; 229 if (value.equals(CURRENT_USERID_PATTERN)) { 230 parameters[idx] = session.getPrincipal().getName(); 231 } else if (value.equals(CURRENT_REPO_PATTERN)) { 232 parameters[idx] = session.getRepositoryName(); 233 } 234 } 235 } 236 237 Map<String, Serializable> props = new HashMap<String, Serializable>(); 238 props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) session); 239 240 if (query == null && StringUtils.isBlank(providerName)) { 241 // provide a defaut query 242 query = "SELECT * from Document"; 243 } 244 245 Long targetPage = null; 246 if (page != null) { 247 targetPage = page.longValue(); 248 } 249 if (currentPageIndex != null) { 250 targetPage = currentPageIndex.longValue(); 251 } 252 Long targetPageSize = null; 253 if (pageSize != null) { 254 targetPageSize = pageSize.longValue(); 255 } 256 257 List<String> targetHighlights = null; 258 if (highlight != null) { 259 String[] highlights = highlight.split(","); 260 targetHighlights = Arrays.asList(highlights); 261 } 262 263 DocumentModel searchDocumentModel = getSearchDocumentModel(session, ppService, providerName, namedParameters); 264 265 PaginableDocumentModelListImpl res; 266 if (query != null) { 267 CoreQueryPageProviderDescriptor desc = new CoreQueryPageProviderDescriptor(); 268 desc.setPattern(query); 269 desc.setQuotePatternParameters(quotePatternParameters); 270 desc.setEscapePatternParameters(escapePatternParameters); 271 if (maxResults != null && !maxResults.isEmpty() && !maxResults.equals("-1")) { 272 // set the maxResults to avoid slowing down queries 273 desc.getProperties().put("maxResults", maxResults); 274 } 275 PageProvider<DocumentModel> pp = (PageProvider<DocumentModel>) ppService.getPageProvider("", desc, 276 searchDocumentModel, sortInfos, targetPageSize, targetPage, props, targetHighlights, null, 277 parameters); 278 res = new PaginableDocumentModelListImpl(pp, documentLinkBuilder); 279 } else { 280 PageProviderDefinition pageProviderDefinition = ppService.getPageProviderDefinition(providerName); 281 // Quick filters management 282 List<QuickFilter> quickFilterList = new ArrayList<>(); 283 if (quickFilters != null && !quickFilters.isEmpty()) { 284 String[] filters = quickFilters.split(","); 285 List<QuickFilter> ppQuickFilters = pageProviderDefinition.getQuickFilters(); 286 for (String filter : filters) { 287 for (QuickFilter quickFilter : ppQuickFilters) { 288 if (quickFilter.getName().equals(filter)) { 289 quickFilterList.add(quickFilter); 290 break; 291 } 292 } 293 } 294 } 295 296 parameters = resolveParameters(parameters); 297 PageProvider<DocumentModel> pp = (PageProvider<DocumentModel>) ppService.getPageProvider(providerName, 298 searchDocumentModel, sortInfos, targetPageSize, targetPage, props, targetHighlights, 299 quickFilterList, parameters); 300 res = new PaginableDocumentModelListImpl(pp, documentLinkBuilder); 301 } 302 if (res.hasError()) { 303 throw new OperationException(res.getErrorMessage()); 304 } 305 return res; 306 } 307 308 /** 309 * Resolves additional parameters that could have been defined in the contribution. 310 * 311 * @param parameters parameters from the operation 312 * @since 5.8 313 */ 314 private Object[] resolveParameters(Object[] parameters) { 315 ActionContext actionContext = (ActionContext) context.get(GetActions.SEAM_ACTION_CONTEXT); 316 if (actionContext == null) { 317 // if no Seam Context has been initialized, don't do evaluation 318 return parameters; 319 } 320 321 // resolve additional parameters 322 PageProviderDefinition ppDef = ppService.getPageProviderDefinition(providerName); 323 String[] params = ppDef.getQueryParameters(); 324 if (params == null) { 325 params = new String[0]; 326 } 327 328 Object[] resolvedParams = new Object[params.length + (parameters != null ? parameters.length : 0)]; 329 330 ELContext elContext = Framework.getService(ELService.class).createELContext(); 331 332 int i = 0; 333 if (parameters != null) { 334 i = parameters.length; 335 System.arraycopy(parameters, 0, resolvedParams, 0, i); 336 } 337 for (int j = 0; j < params.length; j++) { 338 ValueExpression ve = ELActionContext.EXPRESSION_FACTORY.createValueExpression(elContext, params[j], 339 Object.class); 340 resolvedParams[i + j] = ve.getValue(elContext); 341 } 342 return resolvedParams; 343 } 344 345 /** 346 * @since 7.1 347 */ 348 public static DocumentModel getSearchDocumentModel(CoreSession session, PageProviderService pps, 349 String providerName, Properties namedParameters) { 350 // generate search document model if type specified on the definition 351 DocumentModel searchDocumentModel = null; 352 if (!StringUtils.isBlank(providerName)) { 353 PageProviderDefinition pageProviderDefinition = pps.getPageProviderDefinition(providerName); 354 if (pageProviderDefinition != null) { 355 String searchDocType = pageProviderDefinition.getSearchDocumentType(); 356 if (searchDocType != null) { 357 searchDocumentModel = session.createDocumentModel(searchDocType); 358 } else if (pageProviderDefinition.getWhereClause() != null) { 359 // avoid later error on null search doc, in case where clause is only referring to named parameters 360 // (and no namedParameters are given) 361 searchDocumentModel = new SimpleDocumentModel(); 362 } 363 } else { 364 log.error("No page provider definition found for " + providerName); 365 } 366 } 367 368 if (namedParameters != null && !namedParameters.isEmpty()) { 369 // fall back on simple document if no type defined on page provider 370 if (searchDocumentModel == null) { 371 searchDocumentModel = new SimpleDocumentModel(); 372 } 373 for (Map.Entry<String, String> entry : namedParameters.entrySet()) { 374 String key = entry.getKey(); 375 String value = entry.getValue(); 376 try { 377 DocumentHelper.setProperty(session, searchDocumentModel, key, value, true); 378 } catch (PropertyNotFoundException | IOException e) { 379 // assume this is a "pure" named parameter, not part of the search doc schema 380 continue; 381 } 382 } 383 searchDocumentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParameters); 384 } 385 return searchDocumentModel; 386 } 387 388}