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 public SortInfo getSortInfo() { 246 return sortInfo; 247 } 248 249 private Date getCreationDateForVersion(Logs logsService, DocumentModel version) { 250 List<LogEntry> logs = logsService.getLogEntriesFor(version.getId(), filterMap, true); 251 for (LogEntry logEntry : logs) { 252 if (logEntry.getEventId().equals(DocumentEventTypes.DOCUMENT_CREATED)) { 253 return logEntry.getEventDate(); 254 } 255 } 256 return null; 257 } 258 259 private void addLogEntries(List<LogEntry> entries) { 260 if (logEntries != null) { 261 logEntries.addAll(entries); 262 } else { 263 logEntries = entries; 264 } 265 } 266 267 private void addLogEntry(LogEntry entry) { 268 if (logEntries != null) { 269 logEntries.add(entry); 270 } else { 271 logEntries = new ArrayList<LogEntry>(); 272 logEntries.add(entry); 273 } 274 } 275 276 private static FilterMapEntry computeQueryForLogsOnDocUntilDate(Date date) { 277 FilterMapEntry filterByDate = new FilterMapEntry(); 278 filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_DATE); 279 filterByDate.setOperator("<="); 280 filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_DATE); 281 filterByDate.setObject(date); 282 return filterByDate; 283 } 284 285 private static FilterMapEntry computeQueryForLogsOnDocAfterDate(Date date) { 286 FilterMapEntry filterByDate = new FilterMapEntry(); 287 filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_DATE); 288 filterByDate.setOperator(">="); 289 filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_DATE); 290 filterByDate.setObject(date); 291 return filterByDate; 292 } 293 294 private static FilterMapEntry computeQueryForLogsWithEvent(String eventName) { 295 FilterMapEntry filterByDate = new FilterMapEntry(); 296 filterByDate.setColumnName(BuiltinLogEntryData.LOG_EVENT_ID); 297 filterByDate.setOperator("LIKE"); 298 filterByDate.setQueryParameterName(BuiltinLogEntryData.LOG_EVENT_ID); 299 filterByDate.setObject(eventName); 300 return filterByDate; 301 } 302 303 private List<LogEntry> getLogsForDocUntilDate(Logs logsService, String docId, Date date, boolean doDefaultSort) { 304 filterMap = new HashMap<String, FilterMapEntry>(); 305 filterMap.put(BuiltinLogEntryData.LOG_EVENT_DATE, computeQueryForLogsOnDocUntilDate(date)); 306 return logsService.getLogEntriesFor(docId, filterMap, doDefaultSort); 307 } 308 309 private List<LogEntry> getLogsForDocUntilDateWithEvent(Logs logsService, String docId, Date date, String eventName, 310 boolean doDefaultSort) { 311 filterMap = new HashMap<String, FilterMapEntry>(); 312 filterMap.put(BuiltinLogEntryData.LOG_EVENT_DATE, computeQueryForLogsOnDocAfterDate(date)); 313 filterMap.put(BuiltinLogEntryData.LOG_EVENT_ID, computeQueryForLogsWithEvent(eventName)); 314 return logsService.getLogEntriesFor(docId, filterMap, doDefaultSort); 315 316 } 317 318 private class GetVersionInfoForDocumentRunner extends UnrestrictedSessionRunner { 319 320 public String sourceDocForVersionId; 321 322 public DocumentModel version; 323 324 DocumentModel document; 325 326 public GetVersionInfoForDocumentRunner(CoreSession session, DocumentModel document) { 327 super(session); 328 this.document = document; 329 } 330 331 @Override 332 public void run() { 333 version = documentManager.getSourceDocument(document.getRef()); 334 if (version != null) { 335 sourceDocForVersionId = session.getSourceDocument(version.getRef()).getId(); 336 } 337 } 338 } 339 340}