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}