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