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