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