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