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    public List<LogEntry> getLogEntriesFor(String uuid) {
110        if (log.isDebugEnabled()) {
111            log.debug("getLogEntriesFor() UUID=" + uuid);
112        }
113        Query query = em.createNamedQuery("LogEntry.findByDocument");
114        query.setParameter("docUUID", uuid);
115        return doPublish(query.getResultList());
116    }
117
118    @SuppressWarnings("unchecked")
119    @Deprecated
120    public List<LogEntry> getLogEntriesFor(String uuid, Map<String, FilterMapEntry> filterMap, boolean doDefaultSort) {
121        if (log.isDebugEnabled()) {
122            log.debug("getLogEntriesFor() UUID=" + uuid);
123        }
124
125        if (filterMap == null) {
126            filterMap = new HashMap<String, FilterMapEntry>();
127        }
128
129        StringBuilder queryStr = new StringBuilder();
130        queryStr.append(" FROM LogEntry log WHERE log.docUUID=:uuid ");
131
132        Set<String> filterMapKeySet = filterMap.keySet();
133        for (String currentKey : filterMapKeySet) {
134            FilterMapEntry currentFilterMapEntry = filterMap.get(currentKey);
135            String currentOperator = currentFilterMapEntry.getOperator();
136            String currentQueryParameterName = currentFilterMapEntry.getQueryParameterName();
137            String currentColumnName = currentFilterMapEntry.getColumnName();
138
139            if ("LIKE".equals(currentOperator)) {
140                queryStr.append(" AND log.").append(currentColumnName).append(" LIKE :").append(
141                        currentQueryParameterName).append(" ");
142            } else {
143                queryStr.append(" AND log.").append(currentColumnName).append(currentOperator).append(":").append(
144                        currentQueryParameterName).append(" ");
145            }
146        }
147
148        if (doDefaultSort) {
149            queryStr.append(" ORDER BY log.eventDate DESC");
150        }
151
152        Query query = em.createQuery(queryStr.toString());
153
154        query.setParameter("uuid", uuid);
155
156        for (String currentKey : filterMapKeySet) {
157            FilterMapEntry currentFilterMapEntry = filterMap.get(currentKey);
158            String currentOperator = currentFilterMapEntry.getOperator();
159            String currentQueryParameterName = currentFilterMapEntry.getQueryParameterName();
160            Object currentObject = currentFilterMapEntry.getObject();
161
162            if ("LIKE".equals(currentOperator)) {
163                query.setParameter(currentQueryParameterName, "%" + currentObject + "%");
164            } else {
165                query.setParameter(currentQueryParameterName, currentObject);
166            }
167        }
168
169        return doPublish(query.getResultList());
170    }
171
172    /*
173     * (non-Javadoc)
174     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#getLogEntryByID (long)
175     */
176    public LogEntry getLogEntryByID(long id) {
177        if (log.isDebugEnabled()) {
178            log.debug("getLogEntriesFor() logID=" + id);
179        }
180        return doPublish(em.find(LogEntryImpl.class, id));
181    }
182
183    /*
184     * (non-Javadoc)
185     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQueryLogs (java.lang.String, int, int)
186     */
187    @SuppressWarnings("unchecked")
188    public List<LogEntry> nativeQueryLogs(String whereClause, int pageNb, int pageSize) {
189        Query query = em.createQuery("from LogEntry log where " + whereClause);
190        if (pageNb > 1) {
191            query.setFirstResult((pageNb - 1) * pageSize);
192        }else if(pageNb == 0){
193            log.warn("Requested pageNb equals 0 but page index start at 1. Will fallback to fetch the first page");
194        }
195        query.setMaxResults(pageSize);
196        return doPublish(query.getResultList());
197    }
198
199    /*
200     * (non-Javadoc)
201     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQuery(java .lang.String, int, int)
202     */
203    public List<?> nativeQuery(String queryString, int pageNb, int pageSize) {
204        Query query = em.createQuery(queryString);
205        if (pageNb > 1) {
206            query.setFirstResult((pageNb - 1) * pageSize);
207        }
208        query.setMaxResults(pageSize);
209        return doPublishIfEntries(query.getResultList());
210    }
211
212    /*
213     * (non-Javadoc)
214     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#nativeQuery(java .lang.String, java.util.Map, int,
215     * int)
216     */
217    public List<?> nativeQuery(String queryString, Map<String, Object> params, int pageNb, int pageSize) {
218        if (pageSize <= 0) {
219            pageSize = 1000;
220        }
221        Query query = em.createQuery(queryString);
222        for (Entry<String, Object> en : params.entrySet()) {
223            query.setParameter(en.getKey(), en.getValue());
224        }
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#queryLogs(java. lang.String[], java.lang.String)
235     */
236    @SuppressWarnings("unchecked")
237    public List<LogEntry> queryLogs(String[] eventIds, String dateRange) {
238        Date limit;
239        try {
240            limit = DateRangeParser.parseDateRangeQuery(new Date(), dateRange);
241        } catch (AuditQueryException aqe) {
242            aqe.addInfo("Wrong date range query. Query was " + dateRange);
243            throw aqe;
244        }
245
246        String queryStr = "";
247        if (eventIds == null || eventIds.length == 0) {
248            queryStr = "from LogEntry log" + " where log.eventDate >= :limit" + " ORDER BY log.eventDate DESC";
249        } else {
250            String inClause = "(";
251            for (String eventId : eventIds) {
252                inClause += "'" + eventId + "',";
253            }
254            inClause = inClause.substring(0, inClause.length() - 1);
255            inClause += ")";
256
257            queryStr = "from LogEntry log" + " where log.eventId in " + inClause + " AND log.eventDate >= :limit"
258                    + " ORDER BY log.eventDate DESC";
259        }
260
261        if (log.isDebugEnabled()) {
262            log.debug("queryLogs() =" + queryStr);
263        }
264        Query query = em.createQuery(queryStr);
265        query.setParameter("limit", limit);
266
267        return doPublish(query.getResultList());
268    }
269
270    /*
271     * (non-Javadoc)
272     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#queryLogsByPage (java.lang.String[], java.lang.String,
273     * java.lang.String[], java.lang.String, int, int)
274     */
275    public List<LogEntry> queryLogsByPage(String[] eventIds, String dateRange, String[] categories, String path,
276            int pageNb, int pageSize) {
277        Date limit = null;
278        try {
279            limit = DateRangeParser.parseDateRangeQuery(new Date(), dateRange);
280        } catch (AuditQueryException aqe) {
281            aqe.addInfo("Wrong date range query. Query was " + dateRange);
282            throw aqe;
283        }
284        return queryLogsByPage(eventIds, limit, categories, path, pageNb, pageSize);
285    }
286
287    /*
288     * (non-Javadoc)
289     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#queryLogsByPage (java.lang.String[], java.util.Date,
290     * java.lang.String[], java.lang.String, int, int)
291     */
292    @SuppressWarnings("unchecked")
293    public List<LogEntry> queryLogsByPage(String[] eventIds, Date limit, String[] categories, String path, int pageNb,
294            int pageSize) {
295        if (eventIds == null) {
296            eventIds = new String[0];
297        }
298        if (categories == null) {
299            categories = new String[0];
300        }
301
302        StringBuilder queryString = new StringBuilder();
303
304        queryString.append("from LogEntry log where ");
305
306        if (eventIds.length > 0) {
307            String inClause = "(";
308            for (String eventId : eventIds) {
309                inClause += "'" + eventId + "',";
310            }
311            inClause = inClause.substring(0, inClause.length() - 1);
312            inClause += ")";
313
314            queryString.append(" log.eventId IN ").append(inClause);
315            queryString.append(" AND ");
316        }
317        if (categories.length > 0) {
318            String inClause = "(";
319            for (String cat : categories) {
320                inClause += "'" + cat + "',";
321            }
322            inClause = inClause.substring(0, inClause.length() - 1);
323            inClause += ")";
324            queryString.append(" log.category IN ").append(inClause);
325            queryString.append(" AND ");
326        }
327
328        if (path != null && !"".equals(path.trim())) {
329            queryString.append(" log.docPath LIKE '").append(path).append("%'");
330            queryString.append(" AND ");
331        }
332
333        queryString.append(" log.eventDate >= :limit");
334        queryString.append(" ORDER BY log.eventDate DESC");
335
336        Query query = em.createQuery(queryString.toString());
337
338        query.setParameter("limit", limit);
339
340        if (pageNb > 1) {
341            query.setFirstResult((pageNb - 1) * pageSize);
342        }
343        query.setMaxResults(pageSize);
344
345        return doPublish(query.getResultList());
346    }
347
348    /*
349     * (non-Javadoc)
350     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#removeEntries(java .lang.String, java.lang.String)
351     */
352    @Override
353    @SuppressWarnings("unchecked")
354    public int removeEntries(String eventId, String pathPattern) {
355        // TODO extended info cascade delete does not work using HQL, so we
356        // have to delete each
357        // entry by hand.
358        Query query = em.createNamedQuery("LogEntry.findByEventIdAndPath");
359        query.setParameter("eventId", eventId);
360        query.setParameter("pathPattern", pathPattern + "%");
361        int count = 0;
362        for (LogEntry entry : (List<LogEntry>) query.getResultList()) {
363            em.remove(entry);
364            count += 1;
365        }
366        if (log.isDebugEnabled()) {
367            log.debug("removed " + count + " entries from " + pathPattern);
368        }
369        return count;
370    }
371
372    /*
373     * (non-Javadoc)
374     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#countEventsById (java.lang.String)
375     */
376    public Long countEventsById(String eventId) {
377        Query query = em.createNamedQuery("LogEntry.countEventsById");
378        query.setParameter("eventId", eventId);
379        return (Long) query.getSingleResult();
380    }
381
382    /*
383     * (non-Javadoc)
384     * @see org.nuxeo.ecm.platform.audit.service.LogEntryProvider#findEventIds()
385     */
386    @SuppressWarnings("unchecked")
387    public List<String> findEventIds() {
388        Query query = em.createNamedQuery("LogEntry.findEventIds");
389        return (List<String>) query.getResultList();
390    }
391
392}