001/* 002 * Copyright (c) 2006-2012 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * 011 * $Id$ 012 */ 013package org.nuxeo.ecm.platform.routing.web; 014 015import java.io.Serializable; 016import java.sql.Timestamp; 017import java.util.ArrayList; 018import java.util.Calendar; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024 025import org.nuxeo.ecm.core.api.CoreSession; 026import org.nuxeo.ecm.core.api.DocumentModel; 027import org.nuxeo.ecm.core.api.SortInfo; 028import org.nuxeo.ecm.platform.audit.api.AuditReader; 029import org.nuxeo.ecm.platform.audit.api.LogEntry; 030import org.nuxeo.ecm.platform.audit.api.comment.CommentProcessorHelper; 031import org.nuxeo.ecm.platform.query.api.AbstractPageProvider; 032import org.nuxeo.ecm.platform.query.api.PageProvider; 033import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 034import org.nuxeo.ecm.platform.query.api.PredicateDefinition; 035import org.nuxeo.ecm.platform.query.api.PredicateFieldDefinition; 036import org.nuxeo.runtime.api.Framework; 037 038/** 039 * {@link PageProvider} implementation that returns {@link LogEntry} from Audit Service - Used for Route History content 040 * view 041 * 042 * @Since 5.6 043 */ 044public class PreviousRoutesPageProvider extends AbstractPageProvider<LogEntry> implements PageProvider<LogEntry> { 045 046 private static final long serialVersionUID = 1L; 047 048 protected String auditQuery; 049 050 protected Map<String, Object> auditQueryParams; 051 052 public static final String CORE_SESSION_PROPERTY = "coreSession"; 053 054 public static final String UICOMMENTS_PROPERTY = "generateUIComments"; 055 056 public static final String DOC_ID_PROPERTY = "documentID"; 057 058 @Override 059 public String toString() { 060 StringBuffer sb = new StringBuffer(); 061 062 sb.append("\nquery : " + auditQuery); 063 sb.append("\nparams : "); 064 List<String> pNames = new ArrayList<String>(auditQueryParams.keySet()); 065 Collections.sort(pNames); 066 for (String name : pNames) { 067 sb.append("\n "); 068 sb.append(name); 069 sb.append(" : "); 070 sb.append(auditQueryParams.get(name).toString()); 071 } 072 073 return sb.toString(); 074 } 075 076 protected void preprocessCommentsIfNeeded(List<LogEntry> entries) { 077 Serializable preprocess = getProperties().get(UICOMMENTS_PROPERTY); 078 CoreSession session = (CoreSession) getProperties().get(CORE_SESSION_PROPERTY); 079 if (session != null && preprocess != null && "true".equalsIgnoreCase(preprocess.toString())) { 080 CommentProcessorHelper cph = new CommentProcessorHelper(session); 081 cph.processComments(entries); 082 } 083 } 084 085 @SuppressWarnings("unchecked") 086 @Override 087 public List<LogEntry> getCurrentPage() { 088 AuditReader reader = Framework.getService(AuditReader.class); 089 090 buildAuditQuery(true); 091 // TODO: Follow https://jira.nuxeo.com/browse/NXP-17236, native query should use JSON in case of Elasticsearch 092 // audit backend 093 List<LogEntry> entries = (List<LogEntry>) reader.nativeQuery(auditQuery, auditQueryParams, 094 (int) getCurrentPageIndex() + 1, (int) getMinMaxPageSize()); 095 preprocessCommentsIfNeeded(entries); 096 return entries; 097 } 098 099 protected String getSortPart() { 100 StringBuffer sort = new StringBuffer(); 101 if (getSortInfos() != null && getSortInfos().size() > 0) { 102 sort.append(" ORDER BY "); 103 } 104 int index = 0; 105 for (SortInfo si : getSortInfos()) { 106 if (index > 0) { 107 sort.append(" , "); 108 } 109 sort.append(si.getSortColumn()); 110 if (si.getSortAscending()) { 111 sort.append(" ASC "); 112 } else { 113 sort.append(" DESC "); 114 } 115 index++; 116 } 117 return sort.toString(); 118 } 119 120 protected Object convertParam(Object param) { 121 if (param == null) { 122 return null; 123 } 124 // Hibernate does not like Calendar type 125 if (param instanceof Calendar) { 126 return new Timestamp(((Calendar) param).getTime().getTime()); 127 } 128 return param; 129 } 130 131 protected boolean isNonNullParam(Object[] val) { 132 if (val == null) { 133 return false; 134 } 135 for (Object v : val) { 136 if (v != null) { 137 if (v instanceof String) { 138 if (!((String) v).isEmpty()) { 139 return true; 140 } 141 } else if (v instanceof String[]) { 142 if (((String[]) v).length > 0) { 143 return true; 144 } 145 } else { 146 return true; 147 } 148 } 149 } 150 return false; 151 } 152 153 protected String getFixedPart() { 154 if (getDefinition().getWhereClause() == null) { 155 return null; 156 } else { 157 return getDefinition().getWhereClause().getFixedPart(); 158 } 159 } 160 161 protected boolean allowSimplePattern() { 162 return true; 163 } 164 165 protected void buildAuditQuery(boolean includeSort) { 166 PageProviderDefinition def = getDefinition(); 167 Object[] params = getParameters(); 168 169 if (def.getWhereClause() == null) { 170 // Simple Pattern 171 172 if (!allowSimplePattern()) { 173 throw new UnsupportedOperationException("This page provider requires a explicit Where Clause"); 174 } 175 176 String baseQuery = def.getPattern(); 177 178 Map<String, Object> qParams = new HashMap<String, Object>(); 179 for (int i = 0; i < params.length; i++) { 180 baseQuery = baseQuery.replaceFirst("\\?", ":param" + i); 181 qParams.put("param" + i, convertParam(params[i])); 182 } 183 184 if (includeSort) { 185 baseQuery = baseQuery + getSortPart(); 186 } 187 188 auditQuery = baseQuery; 189 auditQueryParams = qParams; 190 191 } else { 192 // Where clause based on DocumentModel 193 194 StringBuilder baseQuery = new StringBuilder("from LogEntry log "); 195 196 // manage fixed part 197 String fixedPart = getFixedPart(); 198 Map<String, Object> qParams = new HashMap<String, Object>(); 199 int idxParam = 0; 200 if (fixedPart != null && !fixedPart.isEmpty()) { 201 while (fixedPart.indexOf("?") > 0) { 202 // Hack for handling parameter in fixed part TODO 203 if (getProperties().get(DOC_ID_PROPERTY) != null) { 204 fixedPart = fixedPart.replaceFirst("\\?", getProperties().get(DOC_ID_PROPERTY).toString()); 205 // Map properties = new HashMap<String, Serializable>(); 206 // properties.put(DOC_ID_PROPERTY, "done"); 207 } else { 208 fixedPart = fixedPart.replaceFirst("\\?", ":param" + idxParam); 209 qParams.put("param" + idxParam, convertParam(params[idxParam])); 210 idxParam++; 211 } 212 } 213 baseQuery.append(" where "); 214 baseQuery.append(fixedPart); 215 } 216 217 // manages predicates 218 DocumentModel searchDocumentModel = getSearchDocumentModel(); 219 if (searchDocumentModel != null) { 220 PredicateDefinition[] predicates = def.getWhereClause().getPredicates(); 221 int idxPredicate = 0; 222 223 for (PredicateDefinition predicate : predicates) { 224 225 // extract data from DocumentModel 226 PredicateFieldDefinition[] fieldDef = predicate.getValues(); 227 Object[] val = new Object[fieldDef.length]; 228 for (int fidx = 0; fidx < fieldDef.length; fidx++) { 229 if (fieldDef[fidx].getXpath() != null) { 230 val[fidx] = searchDocumentModel.getPropertyValue(fieldDef[fidx].getXpath()); 231 } else { 232 val[fidx] = searchDocumentModel.getProperty(fieldDef[fidx].getSchema(), 233 fieldDef[fidx].getName()); 234 } 235 } 236 237 if (!isNonNullParam(val)) { 238 // skip predicate where all values are null 239 continue; 240 } 241 242 if (idxPredicate > 0 || idxParam > 0) { 243 baseQuery.append(" AND "); 244 } else { 245 baseQuery.append(" where "); 246 } 247 248 baseQuery.append(predicate.getParameter()); 249 baseQuery.append(" "); 250 251 if (!predicate.getOperator().equalsIgnoreCase("BETWEEN")) { 252 // don't add the between operation for now 253 baseQuery.append(predicate.getOperator()); 254 } 255 256 if (predicate.getOperator().equalsIgnoreCase("IN")) { 257 baseQuery.append(" ("); 258 259 if (val[0] instanceof Iterable<?>) { 260 Iterable<?> vals = (Iterable<?>) val[0]; 261 Iterator<?> valueIterator = vals.iterator(); 262 263 while (valueIterator.hasNext()) { 264 Object v = valueIterator.next(); 265 qParams.put("param" + idxParam, convertParam(v)); 266 baseQuery.append(" :param" + idxParam); 267 idxParam++; 268 if (valueIterator.hasNext()) { 269 baseQuery.append(","); 270 } 271 } 272 } else if (val[0] instanceof Object[]) { 273 Object[] valArray = (Object[]) val[0]; 274 for (int i = 0; i < valArray.length; i++) { 275 Object v = valArray[i]; 276 qParams.put("param" + idxParam, convertParam(v)); 277 baseQuery.append(" :param" + idxParam); 278 idxParam++; 279 if (i < valArray.length - 1) { 280 baseQuery.append(","); 281 } 282 } 283 } 284 baseQuery.append(" ) "); 285 } else if (predicate.getOperator().equalsIgnoreCase("BETWEEN")) { 286 Object startValue = convertParam(val[0]); 287 Object endValue = null; 288 if (val.length > 1) { 289 endValue = convertParam(val[1]); 290 } 291 if (startValue != null && endValue != null) { 292 baseQuery.append(predicate.getOperator()); 293 baseQuery.append(" :param" + idxParam); 294 qParams.put("param" + idxParam, startValue); 295 idxParam++; 296 baseQuery.append(" AND :param" + idxParam); 297 qParams.put("param" + idxParam, endValue); 298 } else if (startValue == null) { 299 baseQuery.append("<="); 300 baseQuery.append(" :param" + idxParam); 301 qParams.put("param" + idxParam, endValue); 302 } else if (endValue == null) { 303 baseQuery.append(">="); 304 baseQuery.append(" :param" + idxParam); 305 qParams.put("param" + idxParam, startValue); 306 } 307 idxParam++; 308 } else { 309 baseQuery.append(" :param" + idxParam); 310 qParams.put("param" + idxParam, convertParam(val[0])); 311 idxParam++; 312 } 313 314 idxPredicate++; 315 } 316 } 317 318 if (includeSort) { 319 baseQuery.append(getSortPart()); 320 } 321 322 auditQuery = baseQuery.toString(); 323 auditQueryParams = qParams; 324 } 325 } 326 327 @Override 328 public void refresh() { 329 setCurrentPageOffset(0); 330 super.refresh(); 331 } 332 333 @SuppressWarnings("unchecked") 334 @Override 335 public long getResultsCount() { 336 buildAuditQuery(false); 337 AuditReader reader = Framework.getService(AuditReader.class); 338 // TODO: Follow https://jira.nuxeo.com/browse/NXP-17236, native query should use JSON in case of Elasticsearch 339 // audit backend, yet currently there doesn't seem to be any way to do such a count in this case 340 List<Long> res = (List<Long>) reader.nativeQuery("select count(log.id) " + auditQuery, auditQueryParams, 1, 20); 341 resultsCount = res.get(0).longValue(); 342 return resultsCount; 343 } 344 345}