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