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