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