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