001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
016 *
017 * $Id:ContentHistoryActionsBean.java 4487 2006-10-19 22:27:14Z janguenot $
018 */
019
020package org.nuxeo.ecm.platform.audit.web.listener.ejb;
021
022import static org.jboss.seam.ScopeType.EVENT;
023
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.Comparator;
027import java.util.Date;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032import org.apache.commons.lang.StringUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.jboss.seam.annotations.Factory;
036import org.jboss.seam.annotations.In;
037import org.jboss.seam.annotations.Name;
038import org.jboss.seam.annotations.Scope;
039import org.jboss.seam.annotations.web.RequestParameter;
040import org.nuxeo.ecm.core.api.CoreSession;
041import org.nuxeo.ecm.core.api.DocumentModel;
042import org.nuxeo.ecm.core.api.NuxeoException;
043import org.nuxeo.ecm.core.api.SortInfo;
044import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
045import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
046import org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData;
047import org.nuxeo.ecm.platform.audit.api.FilterMapEntry;
048import org.nuxeo.ecm.platform.audit.api.LogEntry;
049import org.nuxeo.ecm.platform.audit.api.Logs;
050import org.nuxeo.ecm.platform.audit.api.comment.CommentProcessorHelper;
051import org.nuxeo.ecm.platform.audit.api.comment.LinkedDocument;
052import org.nuxeo.ecm.platform.audit.web.listener.ContentHistoryActions;
053import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
054import org.nuxeo.runtime.api.Framework;
055
056/**
057 * Content history actions bean.
058 * <p>
059 * :XXX: http://jira.nuxeo.org/browse/NXP-514
060 *
061 * @author <a href="mailto:ja@nuxeo.com">Julien Anguenot</a>
062 */
063@Name("contentHistoryActions")
064@Scope(EVENT)
065public class ContentHistoryActionsBean implements ContentHistoryActions {
066
067    private static final long serialVersionUID = -6110545879809627627L;
068
069    private static final String EVENT_DATE = "eventDate";
070
071    private static final Log log = LogFactory.getLog(ContentHistoryActionsBean.class);
072
073    // @Out(required = false)
074    protected List<LogEntry> logEntries;
075
076    private Map<Long, String> logEntriesComments;
077
078    private Map<Long, LinkedDocument> logEntriesLinkedDocs;
079
080    // :FIXME: Should disappear with Seam 1.1 method params.
081    // @Out(required = false)
082    private List<LogEntry> latestLogEntries;
083
084    // :FIXME: Hardcoded. See interface for more details about the reason
085    protected final int nbLogEntries = 5;
086
087    @In(create = true, required = false)
088    protected transient CoreSession documentManager;
089
090    @In(create = true)
091    private transient NavigationContext navigationContext;
092
093    @RequestParameter("sortColumn")
094    protected String newSortColumn;
095
096    protected SortInfo sortInfo;
097
098    protected Map<String, FilterMapEntry> filterMap = Collections.emptyMap();
099
100    protected Comparator<LogEntry> comparator;
101
102    public ContentHistoryActionsBean() {
103        // init sorting information
104        sortInfo = new SortInfo(EVENT_DATE, false);
105    }
106
107    @Factory(value = "latestLogEntries", scope = EVENT)
108    public List<LogEntry> computeLatestLogEntries() {
109        if (latestLogEntries == null) {
110            if (logEntries == null) {
111                logEntries = computeLogEntries(navigationContext.getCurrentDocument());
112            }
113            if (logEntries != null) {
114                if (logEntries.size() > nbLogEntries) {
115                    latestLogEntries = new ArrayList<LogEntry>(logEntries.subList(0, nbLogEntries));
116                } else {
117                    latestLogEntries = logEntries;
118                }
119            }
120        }
121        return latestLogEntries;
122    }
123
124    @Factory(value = "logEntries", scope = EVENT)
125    public List<LogEntry> computeLogEntries() {
126        if (logEntries == null) {
127            logEntries = computeLogEntries(navigationContext.getCurrentDocument());
128        }
129        return logEntries;
130    }
131
132    @Factory(value = "logEntriesComments", scope = EVENT)
133    public Map<Long, String> computeLogEntriesComments() {
134        if (logEntriesComments == null) {
135            computeLogEntries();
136            postProcessComments(logEntries);
137        }
138        return logEntriesComments;
139    }
140
141    @Factory(value = "logEntriesLinkedDocs", scope = EVENT)
142    public Map<Long, LinkedDocument> computeLogEntrieslinkedDocs() {
143        if (logEntriesLinkedDocs == null) {
144            computeLogEntries();
145            postProcessComments(logEntries);
146        }
147        return logEntriesLinkedDocs;
148    }
149
150    public List<LogEntry> computeLogEntries(DocumentModel document) {
151        if (document == null) {
152            return null;
153        } else {
154            Logs service = Framework.getLocalService(Logs.class);
155            Logs logsBean = service;
156            /*
157             * In case the document is a proxy,meaning is the result of a publishing,to have the history of the document
158             * from which this proxy was created,first we have to get to the version that was created when the document
159             * was publish,and to which the proxy document indicates,and then from that version we have to get to the
160             * root document.
161             */
162            boolean doDefaultSort = comparator == null;
163            if (document.isProxy()) {
164                // all users should have access to logs
165                GetVersionInfoForDocumentRunner runner = new GetVersionInfoForDocumentRunner(documentManager, document);
166                runner.runUnrestricted();
167                if (runner.sourceDocForVersionId == null || runner.version == null) {
168                    String message = "An error occurred while grabbing log entries for " + document.getId();
169                    throw new NuxeoException(message);
170                }
171
172                Date versionCreationDate = getCreationDateForVersion(logsBean, runner.version);
173                // add all the logs from the source document until the
174                // version was created
175                addLogEntries(getLogsForDocUntilDate(logsBean, runner.sourceDocForVersionId, versionCreationDate,
176                        doDefaultSort));
177
178                // !! add the first publishing
179                // event after the version is created; since the publishing
180                // event is logged few milliseconds after the version is
181                // created
182
183                List<LogEntry> publishingLogs = getLogsForDocUntilDateWithEvent(logsBean, runner.sourceDocForVersionId,
184                        versionCreationDate, DocumentEventTypes.DOCUMENT_PUBLISHED, doDefaultSort);
185                if (!publishingLogs.isEmpty()) {
186                    addLogEntry(publishingLogs.get(0));
187                }
188                // add logs from the actual version
189                filterMap = new HashMap<String, FilterMapEntry>();
190                addLogEntries(logsBean.getLogEntriesFor(runner.version.getId(), filterMap, doDefaultSort));
191
192            } else {
193                addLogEntries(logsBean.getLogEntriesFor(document.getId(), filterMap, doDefaultSort));
194            }
195
196            if (log.isDebugEnabled()) {
197                log.debug("logEntries computed .................!");
198            }
199            return logEntries;
200        }
201    }
202
203    public String doSearch() {
204        // toggle newOrderDirection
205        if (StringUtils.isEmpty(newSortColumn)) {
206            newSortColumn = EVENT_DATE;
207        }
208        String sortColumn = sortInfo.getSortColumn();
209        boolean sortAscending = sortInfo.getSortAscending();
210        if (newSortColumn.equals(sortColumn)) {
211            sortAscending = !sortAscending;
212        } else {
213            sortColumn = newSortColumn;
214            sortAscending = true;
215        }
216        sortInfo = new SortInfo(sortColumn, sortAscending);
217        logEntries = null;
218        return null;
219    }
220
221    /**
222     * Post-process log entries comments to add links. e5e7b4ba-0ffb-492d-8bf2-f2f2e6683ae2
223     */
224    private void postProcessComments(List<LogEntry> logEntries) {
225        logEntriesComments = new HashMap<Long, String>();
226        logEntriesLinkedDocs = new HashMap<Long, LinkedDocument>();
227
228        CommentProcessorHelper cph = new CommentProcessorHelper(documentManager);
229
230        if (logEntries == null) {
231            return;
232        }
233
234        for (LogEntry entry : logEntries) {
235            logEntriesComments.put(entry.getId(), cph.getLogComment(entry));
236            LinkedDocument linkedDoc = cph.getLogLinkedDocument(entry);
237            if (linkedDoc != null) {
238                logEntriesLinkedDocs.put(entry.getId(), linkedDoc);
239            }
240        }
241    }
242
243    @Deprecated
244    public String getLogComment(LogEntry entry) {
245        CommentProcessorHelper cph = new CommentProcessorHelper(documentManager);
246        return cph.getLogComment(entry);
247    }
248
249    @Deprecated
250    public LinkedDocument getLogLinkedDocument(LogEntry entry) {
251        CommentProcessorHelper cph = new CommentProcessorHelper(documentManager);
252        return cph.getLogLinkedDocument(entry);
253    }
254
255    public SortInfo getSortInfo() {
256        return sortInfo;
257    }
258
259    private Date getCreationDateForVersion(Logs logsService, DocumentModel version) {
260        List<LogEntry> logs = logsService.getLogEntriesFor(version.getId(), filterMap, true);
261        for (LogEntry logEntry : logs) {
262            if (logEntry.getEventId().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
263                return logEntry.getEventDate();
264            }
265        }
266        return null;
267    }
268
269    private void addLogEntries(List<LogEntry> entries) {
270        if (logEntries != null) {
271            logEntries.addAll(entries);
272        } else {
273            logEntries = entries;
274        }
275    }
276
277    private void addLogEntry(LogEntry entry) {
278        if (logEntries != null) {
279            logEntries.add(entry);
280        } else {
281            logEntries = new ArrayList<LogEntry>();
282            logEntries.add(entry);
283        }
284    }
285
286    private static FilterMapEntry computeQueryForLogsOnDocUntilDate(Date date) {
287        FilterMapEntry filterByDate = new FilterMapEntry();
288        filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_DATE);
289        filterByDate.setOperator("<=");
290        filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_DATE);
291        filterByDate.setObject(date);
292        return filterByDate;
293    }
294
295    private static FilterMapEntry computeQueryForLogsOnDocAfterDate(Date date) {
296        FilterMapEntry filterByDate = new FilterMapEntry();
297        filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_DATE);
298        filterByDate.setOperator(">=");
299        filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_DATE);
300        filterByDate.setObject(date);
301        return filterByDate;
302    }
303
304    private static FilterMapEntry computeQueryForLogsWithEvent(String eventName) {
305        FilterMapEntry filterByDate = new FilterMapEntry();
306        filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_ID);
307        filterByDate.setOperator("LIKE");
308        filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_ID);
309        filterByDate.setObject(eventName);
310        return filterByDate;
311    }
312
313    private List<LogEntry> getLogsForDocUntilDate(Logs logsService, String docId, Date date, boolean doDefaultSort) {
314        filterMap = new HashMap<String, FilterMapEntry>();
315        filterMap.put(BuiltinLogEntryData.LOG_EVENT_DATE, computeQueryForLogsOnDocUntilDate(date));
316        return logsService.getLogEntriesFor(docId, filterMap, doDefaultSort);
317    }
318
319    private List<LogEntry> getLogsForDocUntilDateWithEvent(Logs logsService, String docId, Date date, String eventName,
320            boolean doDefaultSort) {
321        filterMap = new HashMap<String, FilterMapEntry>();
322        filterMap.put(BuiltinLogEntryData.LOG_EVENT_DATE, computeQueryForLogsOnDocAfterDate(date));
323        filterMap.put(BuiltinLogEntryData.LOG_EVENT_ID, computeQueryForLogsWithEvent(eventName));
324        return logsService.getLogEntriesFor(docId, filterMap, doDefaultSort);
325
326    }
327
328    private class GetVersionInfoForDocumentRunner extends UnrestrictedSessionRunner {
329
330        public String sourceDocForVersionId;
331
332        public DocumentModel version;
333
334        DocumentModel document;
335
336        public GetVersionInfoForDocumentRunner(CoreSession session, DocumentModel document) {
337            super(session);
338            this.document = document;
339        }
340
341        @Override
342        public void run() {
343            version = documentManager.getSourceDocument(document.getRef());
344            if (version != null) {
345                sourceDocForVersionId = session.getSourceDocument(version.getRef()).getId();
346            }
347        }
348    }
349
350}