package org.jboss.seam.wiki.core.action; import org.jboss.seam.ScopeType; import org.jboss.seam.international.StatusMessages; import org.jboss.seam.annotations.*; import org.jboss.seam.annotations.datamodel.DataModel; import org.jboss.seam.annotations.datamodel.DataModelSelection; import org.jboss.seam.annotations.security.Restrict; import org.jboss.seam.log.Log; import org.jboss.seam.security.AuthorizationException; import org.jboss.seam.security.Identity; import org.jboss.seam.wiki.core.dao.WikiNodeDAO; import org.jboss.seam.wiki.core.model.WikiDocument; import org.jboss.seam.wiki.core.model.WikiFile; import org.jboss.seam.wiki.core.exception.InvalidWikiRequestException; import org.jboss.seam.wiki.util.Diff; import org.jboss.seam.wiki.util.WikiUtil; import static org.jboss.seam.international.StatusMessage.Severity.ERROR; import static org.jboss.seam.international.StatusMessage.Severity.INFO; import java.io.Serializable; import java.util.List; /** * Diff for historical WikiDocuments. * * TODO: Needs to be generalized to support other WikiFiles, should be easy, except for polymorphic diff() UI, probably * hierarchy of WikiFileDiff actions and some page fragments. Maybe move diff() algorithm into each WikiFile subclass. * * @author Christian Bauer */ @Name("documentHistory") @Scope(ScopeType.CONVERSATION) @AutoCreate public class DocumentHistory implements Serializable { private boolean isInitialized = false; @Logger Log log; @In WikiNodeDAO wikiNodeDAO; @In private StatusMessages statusMessages; @DataModel private List<WikiFile> historicalFileList; public List<WikiFile> getHistoricalFileList() { return historicalFileList; } public void setHistoricalFileList(List<WikiFile> historicalFileList) { this.historicalFileList = historicalFileList; } @DataModelSelection private WikiFile selectedHistoricalFile; public WikiFile getSelectedHistoricalFile() { return selectedHistoricalFile; } public void setSelectedHistoricalFile(WikiFile selectedHistoricalFile) { log.debug("selecting historical file id: " + selectedHistoricalFile.getHistoricalFileId()); this.selectedHistoricalFile = selectedHistoricalFile; } Long fileId; public Long getFileId() { return fileId; } public void setFileId(Long fileId) { this.fileId = fileId; } Long historicalFileId; public Long getHistoricalFileId() { return historicalFileId; } public void setHistoricalFileId(Long historicalFileId) { this.historicalFileId = historicalFileId; } private WikiFile currentFile; public WikiFile getCurrentFile() { return currentFile; } private WikiFile displayedHistoricalFile; public WikiFile getDisplayedHistoricalFile() { return displayedHistoricalFile; } private String diffResult; public String getDiffResult() { return diffResult; } @Factory("historicalFileList") public void initializeHistoricalFileList() { if (historicalFileList == null) { log.debug("initializing list of historical files for file:" + getCurrentFile()); historicalFileList = wikiNodeDAO.findHistoricalFiles(getCurrentFile()); } } public void init() { if (!isInitialized) { if (getFileId() == null) throw new InvalidWikiRequestException("Missing filedId request parameter"); log.debug("initializing document history with file id: " + getFileId()); if (currentFile == null) { log.debug("loading current file: " + getFileId()); currentFile = wikiNodeDAO.findWikiDocument(getFileId()); if (currentFile == null) { throw new org.jboss.seam.framework.EntityNotFoundException(getFileId(), WikiDocument.class); } if (!Identity.instance().hasPermission("Node", "read", currentFile) ) { throw new AuthorizationException("You don't have permission for this operation"); } } initializeHistoricalFileList(); } isInitialized = true; } public void displayHistoricalRevision() { log.debug("displaying historical file id: " + selectedHistoricalFile.getHistoricalFileId()); displayedHistoricalFile = selectedHistoricalFile; diffResult = null; statusMessages.addFromResourceBundleOrDefault( INFO, "lacewiki.msg.DiffOldVersionDisplayed", "Showing historical revision {0}", selectedHistoricalFile.getRevision() ); } public void diffHistoricalRevision() { log.debug("diffing historical file id: " + selectedHistoricalFile.getHistoricalFileId()); String[] a = ((WikiDocument)selectedHistoricalFile).getContent().split("\n"); String[] b = ((WikiDocument)currentFile).getContent().split("\n"); StringBuilder result = new StringBuilder(); List<Diff.Difference> differences = new Diff(a, b).diff(); // TODO: Externalize and i18n these strings for (Diff.Difference diff : differences) { int delStart = diff.getDeletedStart(); int delEnd = diff.getDeletedEnd(); int addStart = diff.getAddedStart(); int addEnd = diff.getAddedEnd(); String type = delEnd != Diff.NONE && addEnd != Diff.NONE ? "changed" : (delEnd == Diff.NONE ? "added" : "deleted"); // Info line result.append("<div class=\"diffInfo\">"); result.append("From "); result.append(delStart == delEnd || delEnd == Diff.NONE ? "line" : "lines"); result.append(" "); result.append(delStart); if (delEnd != Diff.NONE && delStart != delEnd) { result.append(" to ").append(delEnd); } result.append(" ").append(type).append(" to "); result.append(addStart == addEnd || addEnd == Diff.NONE ? "line" : "lines"); result.append(" "); result.append(addStart); if (addEnd != Diff.NONE && addStart != addEnd) { result.append(" to ").append(addEnd); } result.append(":"); result.append("</div>\n"); if (delEnd != Diff.NONE) { result.append("<div class=\"diffDeleted\">"); for (int lnum = delStart; lnum <= delEnd; ++lnum) { result.append( WikiUtil.escapeHtml(a[lnum], false, false) ).append("<br/>"); } result.append("</div>"); if (addEnd != Diff.NONE) { //result.append("----------------------------").append("\n"); } } if (addEnd != Diff.NONE) { result.append("<div class=\"diffAdded\">"); for (int lnum = addStart; lnum <= addEnd; ++lnum) { result.append( WikiUtil.escapeHtml(b[lnum], false, false) ).append("<br/>"); } result.append("</div>"); } } diffResult = result.toString(); statusMessages.addFromResourceBundleOrDefault( INFO, "lacewiki.msg.DiffCreated", "Comparing current revision with historical revision {0}", selectedHistoricalFile.getRevision() ); } // This methods takes the historicalFileId parameter to load a revision from the DB public void diff() { init(); // TODO: Why doesn't Seam execute my page action but instead s:link action="diff" in a fake RENDER RESPONSE?!? displayedHistoricalFile = null; if (historicalFileId == null) return; selectedHistoricalFile = wikiNodeDAO.findHistoricalDocumentAndDetach(getCurrentFile().getHistoricalEntityName(), historicalFileId); if (selectedHistoricalFile == null) { statusMessages.addFromResourceBundleOrDefault( ERROR, "lacewiki.msg.HistoricalNodeNotFound", "Couldn't find historical node: {0}", historicalFileId ); return; } diffHistoricalRevision(); } @Restrict("#{s:hasPermission('Node', 'edit', documentHistory.currentFile)}") public String rollback() { statusMessages.addFromResourceBundleOrDefault( INFO, "lacewiki.msg.RollingBackDocument", "Rolling document back to revision {0}", selectedHistoricalFile.getRevision() ); return "rollback"; } @Restrict("#{s:hasPermission('User', 'isAdmin', currentUser)}") public String purgeHistory() { wikiNodeDAO.removeHistoricalFiles(getCurrentFile()); return "purgedHistory"; } }