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