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