// // Copyright 2009 Robin Komiwes, Bruno Verachten, Christophe Cordenier // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package com.wooki.domain.biz; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import com.ibm.icu.util.Calendar; import com.wooki.Draft; import com.wooki.domain.dao.ActivityDAO; import com.wooki.domain.dao.ChapterDAO; import com.wooki.domain.dao.CommentDAO; import com.wooki.domain.dao.PublicationDAO; import com.wooki.domain.exception.AuthorizationException; import com.wooki.domain.exception.PublicationXmlException; import com.wooki.domain.model.Chapter; import com.wooki.domain.model.Comment; import com.wooki.domain.model.CommentState; import com.wooki.domain.model.Publication; import com.wooki.domain.model.User; import com.wooki.domain.model.activity.Activity; import com.wooki.domain.model.activity.ChapterActivity; import com.wooki.domain.model.activity.ChapterEventType; import com.wooki.domain.model.activity.CommentActivity; import com.wooki.domain.model.activity.CommentEventType; import com.wooki.services.parsers.DOMManager; import com.wooki.services.security.WookiSecurityContext; public class ChapterManagerImpl extends AbstractManager implements ChapterManager { private final ChapterDAO chapterDao; private final ActivityDAO activityDao; private final CommentDAO commentDao; private final PublicationDAO publicationDao; private final DOMManager domManager; private WookiSecurityContext securityCtx; private Logger logger = LoggerFactory.getLogger(ChapterManagerImpl.class); private Map<Long, ReentrantLock> locks = CollectionFactory.newConcurrentMap(); public ChapterManagerImpl(ChapterDAO chapterDAO, ActivityDAO activityDAO, CommentDAO commentDAO, PublicationDAO publicationDAO, DOMManager domManager, ApplicationContext context) { this.chapterDao = chapterDAO; this.activityDao = activityDAO; this.commentDao = commentDAO; this.publicationDao = publicationDAO; this.domManager = domManager; this.securityCtx = context.getBean(WookiSecurityContext.class); } public Comment addComment(Long publicationId, String content, String domId) { // Check security if (!securityCtx.isLoggedIn()) { throw new AuthorizationException( "Only logged user are allowed to add a comments."); } User author = securityCtx.getUser(); if (publicationId == null || content == null) { throw new IllegalArgumentException( "Chapter and comment cannot be null for addition."); } Publication toUpdate = publicationDao.findById(publicationId); Comment comment = new Comment(); comment.setState(CommentState.OPEN); comment.setCreationDate(new Date()); comment.setDomId(domId); comment.setUser(author); comment.setContent(content); comment.setPublication(toUpdate); toUpdate.addComment(comment); this.commentDao.create(comment); // Log activity CommentActivity activity = new CommentActivity(); activity.setBook(toUpdate.getChapter().getBook()); activity.setChapter(toUpdate.getChapter()); activity.setCreationDate(Calendar.getInstance().getTime()); activity.setComment(comment); activity.setType(CommentEventType.POST); activity.setUser(author); this.activityDao.create(activity); return comment; } public Chapter findById(Long chapterId) { return this.chapterDao.findById(chapterId); } public boolean isPublished(Long revision) { return this.publicationDao.isPublished(revision); } public Publication getRevision(Long chapterId, String revision) { assert chapterId != null; if (LAST.equalsIgnoreCase(revision) || revision == null) { return publicationDao .findLastRevision(chapterId); } try { return this.publicationDao.findRevisionById(chapterId, Long.parseLong(revision)); } catch (NumberFormatException nfEx) { throw new IllegalArgumentException("Revision number is invalid"); } } public String getLastContent(Long chapterId) { Publication publication = getRevision(chapterId, null); if (publication != null) { return publication.getContent(); } return null; } public Publication getLastPublishedPublication(Long chapterId) { assert chapterId != null; return publicationDao.findLastPublishedRevision(chapterId); } public String getLastPublishedContent(Long chapterId) { Publication published = getLastPublishedPublication(chapterId); if (published != null) { return published.getContent(); } return null; } public Chapter publishChapter(Long chapterId) { assert chapterId != null; Publication published = publicationDao.findLastRevision(chapterId); Chapter chapter = this.chapterDao.findById(chapterId); if (chapter == null || published == null) { throw new IllegalArgumentException( "Cannot find chapter with id " + chapterId); } // Check security if (!securityCtx.isLoggedIn() || !this.securityCtx.canWrite(chapter.getBook())) { throw new AuthorizationException( "Publish action not authorized"); } // Adapt content String content = null; try { content = domManager.adaptContent(published.getContent(), published.getId()); } catch (PublicationXmlException pxEx) { logger.error("Unable to publish document", pxEx); throw pxEx; } // Check that the logged user is an author of the book User author = securityCtx.getUser(); // Flag last publication as not published Publication lastPublished = publicationDao.findLastPublishedRevision(chapterId); if (lastPublished != null) { lastPublished.setPublished(false); publicationDao.update(lastPublished); } // Publish the last revision published.setLastModified(Calendar.getInstance().getTime()); published.setContent(content); published.setPublished(true); published.setChapter(chapter); publicationDao.update(published); ChapterActivity ca = new ChapterActivity(); ca.setBook(chapter.getBook()); ca.setChapter(published.getChapter()); ca.setType(ChapterEventType.PUBLISHED); ca.setCreationDate(Calendar.getInstance().getTime()); ca.setUser(author); activityDao.create(ca); return chapter; } public void restoreRevision(Long chapterId, String revision) { assert chapterId != null; Publication toRestore = getRevision(chapterId, revision); Chapter chapter = findById(chapterId); toRestore.getContent(); Draft draft = new Draft(); draft.setData(toRestore.getContent()); draft.setTimestamp(chapter.getLastModified()); updateAndPublishContent(chapterId, draft); } public void deleteRevision(Long chapterId, String revision) { assert chapterId != null; List<Publication> publications = publicationDao.listPublication(chapterId); if (publications.size() <= 1) { throw new IllegalStateException( "Chapter must be associated at least to one revision"); } Publication toDelete = getRevision(chapterId, revision); if (toDelete.isPublished()) { throw new IllegalStateException( "Current published revision cannot be deleted"); } publicationDao.delete(toDelete); } public Chapter update(Chapter chapter) { assert chapter != null; return this.chapterDao.update(chapter); } public void updateContent(Long chapterId, Draft draft) { assert chapterId != null; assert draft != null; assert draft.getData() != null; // Update last modified timestamp Date lastModified = Calendar.getInstance().getTime(); ReentrantLock lock = getOrCreateLock(chapterId); Publication publication = publicationDao.findLastRevision(chapterId); lock.lock(); Chapter chapter = null; try { chapter = chapterDao.findById(chapterId); if (chapter.getLastModified() != null && !chapter.getLastModified().equals(draft.getTimestamp())) { throw new ConcurrentModificationException( "Document has been modified by another user in the meantime."); } chapter.setLastModified(lastModified); chapterDao.update(chapter); } finally { lock.unlock(); } // we check the published flag. If set, then this Publication must // be considered as "locked" and we must create a new publication as // the new working copy if (publication == null || (publication != null && publication.isPublished())) { publication = new Publication(); // Security check if (!securityCtx.isLoggedIn() || !this.securityCtx.canWrite(chapter.getBook())) { throw new AuthorizationException( "Publish action not authorized"); } publication.setChapter(chapter); publication.setCreationDate(Calendar.getInstance().getTime()); publicationDao.create(publication); } publication.setContent(draft.getData()); publication.setLastModified(lastModified); publicationDao.update(publication); } public void updateAndPublishContent(Long chapterId, Draft draft) { updateContent(chapterId, draft); publishChapter(chapterId); } public Chapter remove(Long chapterId) { assert chapterId != null; Chapter toDelete = chapterDao.findById(chapterId); if (toDelete == null) { throw new IllegalArgumentException("Cannot find chapter with id " + chapterId); } if (toDelete != null) { // The logged user must be allow to write to the book to delete a chapter in it if (!securityCtx.isLoggedIn() || !this.securityCtx.canWrite(toDelete.getBook())) { throw new AuthorizationException( "Delete action not authorized"); } chapterDao.delete(toDelete); ChapterActivity activity = new ChapterActivity(); activity.setBook(toDelete.getBook()); activity.setCreationDate(Calendar.getInstance().getTime()); activity.setChapter(toDelete); activity.setUser(this.securityCtx.getUser()); activity.setType(ChapterEventType.DELETE); this.activityDao.create(activity); List<Activity> activities = this.activityDao.listAllActivitiesOnChapter(chapterId); if (activities != null) { for (Activity ac : activities) { ac.setResourceUnavailable(true); this.activityDao.update(ac); } } // TODO Delete publication entries also ?? } return toDelete; } public Object[] findPrevious(Long bookId, Long chapterId) { List<Object[]> result = this.chapterDao.findPrevious(bookId, chapterId); if (result != null && result.size() > 0) { return result.get(0); } return null; } public Object[] findNext(Long bookId, Long chapterId) { List<Object[]> result = this.chapterDao.findNext(bookId, chapterId); if (result != null && result.size() > 0) { return result.get(0); } return null; } public List<Chapter> listChapters(Long bookId) { assert bookId != null; return chapterDao.listChapters(bookId); } public List<Chapter> listChaptersInfo(Long bookId) { assert bookId != null; return chapterDao.listChapterInfo(bookId); } public List<Publication> listPublicationInfo(Long chapterId) { assert chapterId != null; return publicationDao.listPublication(chapterId); } private synchronized ReentrantLock getOrCreateLock(Long chapterId) { assert chapterId != null; if (locks.containsKey(chapterId)) { return locks.get(chapterId); } ReentrantLock lock = new ReentrantLock(); locks.put(chapterId, lock); return lock; } }