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