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 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021package org.nuxeo.elasticsearch.audit.pageprovider; 022 023import java.io.IOException; 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.List; 027 028import org.apache.commons.lang.StringUtils; 029import org.elasticsearch.action.search.SearchRequestBuilder; 030import org.elasticsearch.action.search.SearchResponse; 031import org.elasticsearch.search.SearchHit; 032import org.elasticsearch.search.SearchHits; 033import org.elasticsearch.search.sort.SortOrder; 034import org.nuxeo.ecm.core.api.CoreSession; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.ecm.core.api.SortInfo; 037import org.nuxeo.ecm.platform.audit.api.LogEntry; 038import org.nuxeo.ecm.platform.audit.api.comment.CommentProcessorHelper; 039import org.nuxeo.ecm.platform.audit.service.AuditBackend; 040import org.nuxeo.ecm.platform.audit.service.NXAuditEventsService; 041import org.nuxeo.ecm.platform.query.api.AbstractPageProvider; 042import org.nuxeo.ecm.platform.query.api.PageProvider; 043import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 044import org.nuxeo.ecm.platform.query.api.QuickFilter; 045import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition; 046import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder; 047import org.nuxeo.elasticsearch.audit.ESAuditBackend; 048import org.nuxeo.elasticsearch.audit.io.AuditEntryJSONReader; 049import org.nuxeo.runtime.api.Framework; 050 051public class ESAuditPageProvider extends AbstractPageProvider<LogEntry> implements PageProvider<LogEntry> { 052 053 private static final long serialVersionUID = 1L; 054 055 protected SearchRequestBuilder searchBuilder; 056 057 public static final String CORE_SESSION_PROPERTY = "coreSession"; 058 059 public static final String UICOMMENTS_PROPERTY = "generateUIComments"; 060 061 protected static String emptyQuery = "{ \"match_all\" : { }\n }"; 062 063 @Override 064 public String toString() { 065 buildAuditQuery(true); 066 StringBuffer sb = new StringBuffer(); 067 sb.append(searchBuilder.toString()); 068 return sb.toString(); 069 } 070 071 protected CoreSession getCoreSession() { 072 Object session = getProperties().get(CORE_SESSION_PROPERTY); 073 if (session != null && session instanceof CoreSession) { 074 return (CoreSession) session; 075 } 076 return null; 077 } 078 079 protected void preprocessCommentsIfNeeded(List<LogEntry> entries) { 080 Serializable preprocess = getProperties().get(UICOMMENTS_PROPERTY); 081 082 if (preprocess != null && "true".equalsIgnoreCase(preprocess.toString())) { 083 CoreSession session = getCoreSession(); 084 if (session != null) { 085 CommentProcessorHelper cph = new CommentProcessorHelper(session); 086 cph.processComments(entries); 087 } 088 } 089 } 090 091 @Override 092 public List<LogEntry> getCurrentPage() { 093 094 buildAuditQuery(true); 095 searchBuilder.setFrom((int) (getCurrentPageIndex() * pageSize)); 096 searchBuilder.setSize((int) getMinMaxPageSize()); 097 098 for (SortInfo sortInfo : getSortInfos()) { 099 searchBuilder.addSort(sortInfo.getSortColumn(), 100 sortInfo.getSortAscending() ? SortOrder.ASC : SortOrder.DESC); 101 } 102 103 SearchResponse searchResponse = searchBuilder.execute().actionGet(); 104 List<LogEntry> entries = new ArrayList<>(); 105 SearchHits hits = searchResponse.getHits(); 106 107 // set total number of hits ? 108 setResultsCount(hits.getTotalHits()); 109 110 for (SearchHit hit : hits) { 111 try { 112 entries.add(AuditEntryJSONReader.read(hit.getSourceAsString())); 113 } catch (IOException e) { 114 log.error("Error while reading Audit Entry from ES", e); 115 } 116 } 117 preprocessCommentsIfNeeded(entries); 118 119 long t0 = System.currentTimeMillis(); 120 121 CoreSession session = getCoreSession(); 122 if (session != null) { 123 // send event for statistics ! 124 fireSearchEvent(session.getPrincipal(), searchBuilder.toString(), entries, System.currentTimeMillis() - t0); 125 } 126 127 return entries; 128 } 129 130 protected boolean isNonNullParam(Object[] val) { 131 if (val == null) { 132 return false; 133 } 134 for (Object v : val) { 135 if (v != null) { 136 if (v instanceof String) { 137 if (!((String) v).isEmpty()) { 138 return true; 139 } 140 } else if (v instanceof String[]) { 141 if (((String[]) v).length > 0) { 142 return true; 143 } 144 } else { 145 return true; 146 } 147 } 148 } 149 return false; 150 } 151 152 protected String getFixedPart() { 153 if (getDefinition().getWhereClause() == null) { 154 return null; 155 } else { 156 String fixedPart = getDefinition().getWhereClause().getFixedPart(); 157 if (fixedPart == null || fixedPart.isEmpty()) { 158 fixedPart = emptyQuery; 159 } 160 return fixedPart; 161 } 162 } 163 164 protected boolean allowSimplePattern() { 165 return true; 166 } 167 168 protected ESAuditBackend getESBackend() { 169 NXAuditEventsService audit = (NXAuditEventsService) Framework.getRuntime() 170 .getComponent(NXAuditEventsService.NAME); 171 AuditBackend backend = audit.getBackend(); 172 if (backend instanceof ESAuditBackend) { 173 return (ESAuditBackend) backend; 174 } 175 throw new NuxeoException( 176 "Unable to use ESAuditPageProvider if audit service is not configured to run with ElasticSearch"); 177 } 178 179 protected void buildAuditQuery(boolean includeSort) { 180 PageProviderDefinition def = getDefinition(); 181 Object[] params = getParameters(); 182 List<QuickFilter> quickFilters = getQuickFilters(); 183 String quickFiltersClause = ""; 184 185 if (quickFilters != null && !quickFilters.isEmpty()) { 186 for (QuickFilter quickFilter : quickFilters) { 187 String clause = quickFilter.getClause(); 188 if (!quickFiltersClause.isEmpty() && clause != null) { 189 quickFiltersClause = NXQLQueryBuilder.appendClause(quickFiltersClause, clause); 190 } else { 191 quickFiltersClause = clause != null ? clause : ""; 192 } 193 } 194 } 195 196 WhereClauseDefinition whereClause = def.getWhereClause(); 197 if (whereClause == null) { 198 // Simple Pattern 199 200 if (!allowSimplePattern()) { 201 throw new UnsupportedOperationException("This page provider requires a explicit Where Clause"); 202 } 203 String originalPattern = def.getPattern(); 204 String pattern = quickFiltersClause.isEmpty() ? originalPattern 205 : StringUtils.containsIgnoreCase(originalPattern, " WHERE ") 206 ? NXQLQueryBuilder.appendClause(originalPattern, quickFiltersClause) 207 : originalPattern + " WHERE " + quickFiltersClause; 208 209 String baseQuery = getESBackend().expandQueryVariables(pattern, params); 210 searchBuilder = getESBackend().buildQuery(baseQuery, null); 211 } else { 212 213 // Add the quick filters clauses to the fixed part 214 String fixedPart = getFixedPart(); 215 if (!StringUtils.isBlank(quickFiltersClause)) { 216 fixedPart = (!StringUtils.isBlank(fixedPart)) 217 ? NXQLQueryBuilder.appendClause(fixedPart, quickFiltersClause) : quickFiltersClause; 218 } 219 220 // Where clause based on DocumentModel 221 String baseQuery = getESBackend().expandQueryVariables(fixedPart, params); 222 searchBuilder = getESBackend().buildSearchQuery(baseQuery, whereClause.getPredicates(), 223 getSearchDocumentModel()); 224 } 225 } 226 227 @Override 228 public void refresh() { 229 setCurrentPageOffset(0); 230 super.refresh(); 231 } 232 233 @Override 234 public long getResultsCount() { 235 return resultsCount; 236 } 237 238 @Override 239 public List<SortInfo> getSortInfos() { 240 241 // because ContentView can reuse PageProVider without redefining columns 242 // ensure compat for ContentView configured with JPA log.* sort syntax 243 List<SortInfo> sortInfos = super.getSortInfos(); 244 for (SortInfo si : sortInfos) { 245 if (si.getSortColumn().startsWith("log.")) { 246 si.setSortColumn(si.getSortColumn().substring(4)); 247 } 248 } 249 return sortInfos; 250 } 251 252}