001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Stephane Lacoin (Nuxeo EP Software Engineer)
016 */
017
018package org.nuxeo.ecm.platform.audit.service;
019
020import java.util.Date;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Set;
026
027import javax.persistence.EntityManager;
028import javax.persistence.Query;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.ecm.platform.audit.api.FilterMapEntry;
033import org.nuxeo.ecm.platform.audit.api.LogEntry;
034import org.nuxeo.ecm.platform.audit.api.query.AuditQueryException;
035import org.nuxeo.ecm.platform.audit.api.query.DateRangeParser;
036import org.nuxeo.ecm.platform.audit.impl.LogEntryImpl;
037
038public class LogEntryProvider implements BaseLogEntryProvider {
039
040    private static final Log log = LogFactory.getLog(LogEntryProvider.class);
041
042    protected final EntityManager em;
043
044    private LogEntryProvider(EntityManager em) {
045        this.em = em;
046    }
047
048    public static LogEntryProvider createProvider(EntityManager em) {
049        return new LogEntryProvider(em);
050    }
051
052    protected void doPersist(LogEntry entry) {
053        // Set the log date in java right before saving to the database. We
054        // cannot set a static column definition to
055        // "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" as MS SQL Server does not
056        // support the TIMESTAMP column type and generating a dynamic
057        // persistence configuration that would depend on the database is too
058        // complicated.
059        entry.setLogDate(new Date());
060        em.persist(entry);
061    }
062
063    protected List<?> doPublishIfEntries(List<?> entries) {
064        if (entries == null || entries.size() == 0) {
065            return entries;
066        }
067        Object entry = entries.get(0);
068        if (entry instanceof LogEntry) {
069            for (Object logEntry : entries) {
070                doPublish((LogEntry) logEntry);
071            }
072        }
073        return entries;
074    }
075
076    protected List<LogEntry> doPublish(List<LogEntry> entries) {
077        for (LogEntry entry : entries) {
078            doPublish(entry);
079        }
080        return entries;
081    }
082
083    protected LogEntry doPublish(LogEntry entry) {
084        if (entry.getExtendedInfos() != null) {
085            entry.getExtendedInfos().size(); // force lazy loading
086        }
087        return entry;
088    }
089
090    /*
091     * (non-Javadoc)
092     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#addLogEntry(org
093     * .nuxeo.ecm.platform.audit.api.LogEntry)
094     */
095    @Override
096    public void addLogEntry(LogEntry entry) {
097        doPersist(entry);
098    }
099
100    public void addLogEntries(List<LogEntry> entries) {
101        for (LogEntry entry : entries) {
102            doPersist(entry);
103        }
104    }
105
106    @SuppressWarnings("unchecked")
107    public List<LogEntry> getLogEntriesFor(String uuid) {
108        if (log.isDebugEnabled()) {
109            log.debug("getLogEntriesFor() UUID=" + uuid);
110        }
111        Query query = em.createNamedQuery("LogEntry.findByDocument");
112        query.setParameter("docUUID", uuid);
113        return doPublish(query.getResultList());
114    }
115
116    @SuppressWarnings("unchecked")
117    @Deprecated
118    public List<LogEntry> getLogEntriesFor(String uuid, Map<String, FilterMapEntry> filterMap, boolean doDefaultSort) {
119        if (log.isDebugEnabled()) {
120            log.debug("getLogEntriesFor() UUID=" + uuid);
121        }
122
123        if (filterMap == null) {
124            filterMap = new HashMap<String, FilterMapEntry>();
125        }
126
127        StringBuilder queryStr = new StringBuilder();
128        queryStr.append(" FROM LogEntry log WHERE log.docUUID=:uuid ");
129
130        Set<String> filterMapKeySet = filterMap.keySet();
131        for (String currentKey : filterMapKeySet) {
132            FilterMapEntry currentFilterMapEntry = filterMap.get(currentKey);
133            String currentOperator = currentFilterMapEntry.getOperator();
134            String currentQueryParameterName = currentFilterMapEntry.getQueryParameterName();
135            String currentColumnName = currentFilterMapEntry.getColumnName();
136
137            if ("LIKE".equals(currentOperator)) {
138                queryStr.append(" AND log.").append(currentColumnName).append(" LIKE :").append(
139                        currentQueryParameterName).append(" ");
140            } else {
141                queryStr.append(" AND log.").append(currentColumnName).append(currentOperator).append(":").append(
142                        currentQueryParameterName).append(" ");
143            }
144        }
145
146        if (doDefaultSort) {
147            queryStr.append(" ORDER BY log.eventDate DESC");
148        }
149
150        Query query = em.createQuery(queryStr.toString());
151
152        query.setParameter("uuid", uuid);
153
154        for (String currentKey : filterMapKeySet) {
155            FilterMapEntry currentFilterMapEntry = filterMap.get(currentKey);
156            String currentOperator = currentFilterMapEntry.getOperator();
157            String currentQueryParameterName = currentFilterMapEntry.getQueryParameterName();
158            Object currentObject = currentFilterMapEntry.getObject();
159
160            if ("LIKE".equals(currentOperator)) {
161                query.setParameter(currentQueryParameterName, "%" + currentObject + "%");
162            } else {
163                query.setParameter(currentQueryParameterName, currentObject);
164            }
165        }
166
167        return doPublish(query.getResultList());
168    }
169
170    /*
171     * (non-Javadoc)
172     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#getLogEntryByID (long)
173     */
174    public LogEntry getLogEntryByID(long id) {
175        if (log.isDebugEnabled()) {
176            log.debug("getLogEntriesFor() logID=" + id);
177        }
178        return doPublish(em.find(LogEntryImpl.class, id));
179    }
180
181    /*
182     * (non-Javadoc)
183     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQueryLogs (java.lang.String, int, int)
184     */
185    @SuppressWarnings("unchecked")
186    public List<LogEntry> nativeQueryLogs(String whereClause, int pageNb, int pageSize) {
187        Query query = em.createQuery("from LogEntry log where " + whereClause);
188        if (pageNb > 1) {
189            query.setFirstResult((pageNb - 1) * pageSize);
190        }else if(pageNb == 0){
191            log.warn("Requested pageNb equals 0 but page index start at 1. Will fallback to fetch the first page");
192        }
193        query.setMaxResults(pageSize);
194        return doPublish(query.getResultList());
195    }
196
197    /*
198     * (non-Javadoc)
199     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQuery(java .lang.String, int, int)
200     */
201    public List<?> nativeQuery(String queryString, int pageNb, int pageSize) {
202        Query query = em.createQuery(queryString);
203        if (pageNb > 1) {
204            query.setFirstResult((pageNb - 1) * pageSize);
205        }
206        query.setMaxResults(pageSize);
207        return doPublishIfEntries(query.getResultList());
208    }
209
210    /*
211     * (non-Javadoc)
212     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQuery(java .lang.String, java.util.Map, int,
213     * int)
214     */
215    public List<?> nativeQuery(String queryString, Map<String, Object> params, int pageNb, int pageSize) {
216        if (pageSize <= 0) {
217            pageSize = 1000;
218        }
219        Query query = em.createQuery(queryString);
220        for (Entry<String, Object> en : params.entrySet()) {
221            query.setParameter(en.getKey(), en.getValue());
222        }
223        if (pageNb > 1) {
224            query.setFirstResult((pageNb - 1) * pageSize);
225        }
226        query.setMaxResults(pageSize);
227        return doPublishIfEntries(query.getResultList());
228    }
229
230    /*
231     * (non-Javadoc)
232     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#queryLogs(java. lang.String[], java.lang.String)
233     */
234    @SuppressWarnings("unchecked")
235    public List<LogEntry> queryLogs(String[] eventIds, String dateRange) {
236        Date limit;
237        try {
238            limit = DateRangeParser.parseDateRangeQuery(new Date(), dateRange);
239        } catch (AuditQueryException aqe) {
240            aqe.addInfo("Wrong date range query. Query was " + dateRange);
241            throw aqe;
242        }
243
244        String queryStr = "";
245        if (eventIds == null || eventIds.length == 0) {
246            queryStr = "from LogEntry log" + " where log.eventDate >= :limit" + " ORDER BY log.eventDate DESC";
247        } else {
248            String inClause = "(";
249            for (String eventId : eventIds) {
250                inClause += "'" + eventId + "',";
251            }
252            inClause = inClause.substring(0, inClause.length() - 1);
253            inClause += ")";
254
255            queryStr = "from LogEntry log" + " where log.eventId in " + inClause + " AND log.eventDate >= :limit"
256                    + " ORDER BY log.eventDate DESC";
257        }
258
259        if (log.isDebugEnabled()) {
260            log.debug("queryLogs() =" + queryStr);
261        }
262        Query query = em.createQuery(queryStr);
263        query.setParameter("limit", limit);
264
265        return doPublish(query.getResultList());
266    }
267
268    /*
269     * (non-Javadoc)
270     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#queryLogsByPage (java.lang.String[], java.lang.String,
271     * java.lang.String[], java.lang.String, int, int)
272     */
273    public List<LogEntry> queryLogsByPage(String[] eventIds, String dateRange, String[] categories, String path,
274            int pageNb, int pageSize) {
275        Date limit = null;
276        try {
277            limit = DateRangeParser.parseDateRangeQuery(new Date(), dateRange);
278        } catch (AuditQueryException aqe) {
279            aqe.addInfo("Wrong date range query. Query was " + dateRange);
280            throw aqe;
281        }
282        return queryLogsByPage(eventIds, limit, categories, path, pageNb, pageSize);
283    }
284
285    /*
286     * (non-Javadoc)
287     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#queryLogsByPage (java.lang.String[], java.util.Date,
288     * java.lang.String[], java.lang.String, int, int)
289     */
290    @SuppressWarnings("unchecked")
291    public List<LogEntry> queryLogsByPage(String[] eventIds, Date limit, String[] categories, String path, int pageNb,
292            int pageSize) {
293        if (eventIds == null) {
294            eventIds = new String[0];
295        }
296        if (categories == null) {
297            categories = new String[0];
298        }
299
300        StringBuilder queryString = new StringBuilder();
301
302        queryString.append("from LogEntry log where ");
303
304        if (eventIds.length > 0) {
305            String inClause = "(";
306            for (String eventId : eventIds) {
307                inClause += "'" + eventId + "',";
308            }
309            inClause = inClause.substring(0, inClause.length() - 1);
310            inClause += ")";
311
312            queryString.append(" log.eventId IN ").append(inClause);
313            queryString.append(" AND ");
314        }
315        if (categories.length > 0) {
316            String inClause = "(";
317            for (String cat : categories) {
318                inClause += "'" + cat + "',";
319            }
320            inClause = inClause.substring(0, inClause.length() - 1);
321            inClause += ")";
322            queryString.append(" log.category IN ").append(inClause);
323            queryString.append(" AND ");
324        }
325
326        if (path != null && !"".equals(path.trim())) {
327            queryString.append(" log.docPath LIKE '").append(path).append("%'");
328            queryString.append(" AND ");
329        }
330
331        queryString.append(" log.eventDate >= :limit");
332        queryString.append(" ORDER BY log.eventDate DESC");
333
334        Query query = em.createQuery(queryString.toString());
335
336        query.setParameter("limit", limit);
337
338        if (pageNb > 1) {
339            query.setFirstResult((pageNb - 1) * pageSize);
340        }
341        query.setMaxResults(pageSize);
342
343        return doPublish(query.getResultList());
344    }
345
346    /*
347     * (non-Javadoc)
348     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#removeEntries(java .lang.String, java.lang.String)
349     */
350    @Override
351    @SuppressWarnings("unchecked")
352    public int removeEntries(String eventId, String pathPattern) {
353        // TODO extended info cascade delete does not work using HQL, so we
354        // have to delete each
355        // entry by hand.
356        Query query = em.createNamedQuery("LogEntry.findByEventIdAndPath");
357        query.setParameter("eventId", eventId);
358        query.setParameter("pathPattern", pathPattern + "%");
359        int count = 0;
360        for (LogEntry entry : (List<LogEntry>) query.getResultList()) {
361            em.remove(entry);
362            count += 1;
363        }
364        if (log.isDebugEnabled()) {
365            log.debug("removed " + count + " entries from " + pathPattern);
366        }
367        return count;
368    }
369
370    /*
371     * (non-Javadoc)
372     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#countEventsById (java.lang.String)
373     */
374    public Long countEventsById(String eventId) {
375        Query query = em.createNamedQuery("LogEntry.countEventsById");
376        query.setParameter("eventId", eventId);
377        return (Long) query.getSingleResult();
378    }
379
380    /*
381     * (non-Javadoc)
382     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#findEventIds()
383     */
384    @SuppressWarnings("unchecked")
385    public List<String> findEventIds() {
386        Query query = em.createNamedQuery("LogEntry.findEventIds");
387        return (List<String>) query.getResultList();
388    }
389
390}