/* * Copyright (C) 2010 - 2014 Converge Consulting * Copyright (C) 2015 Allan Lykke Christensen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package dk.i2m.converge.ejb.facades; import dk.i2m.converge.core.workflow.WorkflowStateTransitionException; import dk.i2m.converge.core.DataNotFoundException; import dk.i2m.converge.core.content.*; import dk.i2m.converge.core.content.catalogue.MediaItem; import dk.i2m.converge.core.logging.LogSeverity; import dk.i2m.converge.core.plugin.WorkflowAction; import dk.i2m.converge.core.search.QueueEntryOperation; import dk.i2m.converge.core.search.QueueEntryType; import dk.i2m.converge.core.security.SystemPrivilege; import dk.i2m.converge.core.security.UserAccount; import dk.i2m.converge.core.security.UserRole; import dk.i2m.converge.core.utils.BeanComparator; import dk.i2m.converge.core.views.CurrentAssignment; import dk.i2m.converge.core.views.InboxView; import dk.i2m.converge.core.workflow.*; import dk.i2m.converge.ejb.services.*; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Resource; import javax.ejb.*; import javax.persistence.OptimisticLockException; /** * Enterprise session bean providing a facade to working with {@link NewsItem}s. * * @author Allan Lykke Christensen */ @Stateless public class NewsItemFacadeBean implements NewsItemFacadeLocal { private static final Logger LOG = Logger.getLogger(NewsItemFacadeBean.class. getName()); @EJB private ConfigurationServiceLocal cfgService; @EJB private DaoServiceLocal daoService; @EJB private UserFacadeLocal userFacade; @EJB private UserServiceLocal userService; @EJB private NotificationServiceLocal notificationService; @EJB private OutletFacadeLocal outletFacade; @EJB private SearchEngineLocal searchEngine; @EJB private PluginContextBeanLocal pluginContext; @Resource private SessionContext ctx; /** * Starts a new {@link NewsItem}. * * @param newsItem {@link NewsItem} to start. * @return Started {@link NewsItem} * @throws WorkflowStateTransitionException If the workflow could not be * started for the <code>newsItem</code> */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public NewsItem start(NewsItem newsItem) throws WorkflowStateTransitionException { if (newsItem == null) { throw new WorkflowStateTransitionException("NewsItem not available"); } if (newsItem.getId() != null) { throw new DuplicateExecutionException("NewsItem #" + newsItem.getId() + " already started"); } if (newsItem.getOutlet() == null) { throw new WorkflowStateTransitionException("Outlet must be selected"); } WorkflowState startState = newsItem.getOutlet().getWorkflow(). getStartState(); boolean actorSet = false; UserRole requiredRole = startState.getActorRole(); for (NewsItemActor actor : newsItem.getActors()) { if (actor.getRole().equals(requiredRole)) { actorSet = true; break; } } if (!actorSet) { throw new MissingActorException("Actor with role " + requiredRole. getName() + " is missing", requiredRole); } try { daoService.findById(NewsItem.class, newsItem.getId()); throw new DuplicateExecutionException("NewsItem #" + newsItem.getId() + " already exist"); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "News Item ID Does NOT Exist"); LOG.log(Level.FINEST, "", ex); } String uid = ctx.getCallerPrincipal().getName(); UserAccount ua = null; try { ua = userService.findById(uid); } catch (Exception ex) { throw new WorkflowStateTransitionException( "Could not resolve transition initator", ex); } try { Calendar now = Calendar.getInstance(); WorkflowStateTransition transition = new WorkflowStateTransition( newsItem, now, startState, ua); transition.setStoryVersion(""); transition.setComment(""); newsItem.getHistory().add(transition); newsItem.setCurrentState(startState); newsItem.setUpdated(now); newsItem.setCreated(now); newsItem.setPrecalculatedWordCount(newsItem.getWordCount()); newsItem.setPrecalculatedCurrentActor(newsItem.getCurrentActor()); newsItem = daoService.create(newsItem); List<UserAccount> usersToNotify = new ArrayList<UserAccount>(); WorkflowState state = newsItem.getCurrentState(); switch (state.getPermission()) { case USER: for (NewsItemActor actor : newsItem.getActors()) { if (actor.getRole().equals(state.getActorRole())) { usersToNotify.add(actor.getUser()); } } break; case GROUP: for (UserAccount actor : userService.findAll()) { if (actor.getUserRoles().contains(state.getActorRole())) { usersToNotify.add(actor); } } break; } for (UserAccount userToNotify : usersToNotify) { if (!userToNotify.equals(ua)) { String msgPattern = cfgService.getMessage( "notification_MSG_STORY_ASSIGNED"); SimpleDateFormat sdf = new SimpleDateFormat(cfgService. getMessage("FORMAT_SHORT_DATE_AND_TIME")); sdf.setTimeZone(userToNotify.getTimeZone()); String date = sdf.format(newsItem.getDeadline().getTime()); Object[] args = new Object[]{newsItem.getTitle(), date, ua. getFullName()}; String msg = MessageFormat.format(msgPattern, args); notificationService.create(userToNotify, msg); } } return newsItem; } catch (Exception ex) { throw new WorkflowStateTransitionException(ex); } } /** * Promotes the {@link NewsItem} in the workflow. * * @param newsItem {@link NewsItem} to promote * @param step Unique identifier of the next step * @param comment Comment from the sender * @return Promoted {@link NewsItem} * @throws WorkflowStateTransitionException If the next step is not legal */ @Override public NewsItem step(NewsItem newsItem, Long step, String comment) throws WorkflowStateTransitionException { UserAccount ua = getCurrentWorkflowUser(); return step(newsItem, ua, step, comment); } /** * Promotes the {@link NewsItem} in the workflow. * * @param newsItem {@link NewsItem} to promote * @param ua {@link UserAccount} that is executing the transition * @param step Unique identifier of the next step * @param comment Comment from the sender * @return Promoted {@link NewsItem} * @throws WorkflowStateTransitionException If the next step is not legal */ @Override public NewsItem step(NewsItem newsItem, UserAccount ua, Long step, String comment) throws WorkflowStateTransitionException { pluginContext.log(LogSeverity.FINE, "Workflow transition for news item #{0} using workflow step #{1}", new Object[]{newsItem.getId(), step}, newsItem, newsItem.getId()); try { WorkflowStep transitionStep = daoService.findById(WorkflowStep.class, step); newsItem.addTransition(new WorkflowStateTransition(newsItem, transitionStep, ua, comment)); newsItem = checkin(newsItem, ua); executeWorkflowStepActions(newsItem, transitionStep, ua); return newsItem; } catch (DataNotFoundException ex) { throw new WorkflowStateTransitionException(ex.getMessage(), ex); } catch (LockingException ex) { throw new WorkflowStateTransitionException(ex.getMessage(), ex); } } /** * Promotes the {@link NewsItem} in the workflow regardless of any options. * Note that no workflow step actions are executed using this variant of the * {@link #step} * * @param newsItem {@link NewsItem} to promote * @param state New state * @param comment Comment from the sender * @return Promoted {@link NewsItem} * @throws WorkflowStateTransitionException If the transition could not be * completed */ @Override public NewsItem step(NewsItem newsItem, WorkflowState state, String comment) throws WorkflowStateTransitionException { LOG.log(Level.INFO, "Changing state from {1} to {2} for news item #{0}", new Object[]{newsItem.getId(), newsItem.getCurrentState().getName(), state.getName()}); try { UserAccount userAccount = getCurrentWorkflowUser(); WorkflowState nextState = daoService.findById(WorkflowState.class, state.getId()); newsItem.addTransition(new WorkflowStateTransition(newsItem, userAccount, comment, nextState)); newsItem = checkin(newsItem); return newsItem; } catch (DataNotFoundException ex) { throw new WorkflowStateTransitionException("Invalid workflow state", ex); } catch (LockingException ex) { throw new WorkflowStateTransitionException(ex); } } /** * Finds the current assignments of a given user. * <p/> * @param username Username of the user * @return {@link List} of current assignments */ @Override public List<CurrentAssignment> findCurrentAssignments(String username) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("user", ua).and("permission", WorkflowStatePermission.GROUP).parameters(); return daoService.findWithNamedQuery( NewsItem.VIEW_CURRENT_ASSIGNMENTS, params); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Specified user {0} is unknown", username); return Collections.EMPTY_LIST; } } /** * Gets items in the inbox for a particular user. * <p/> * @param username Username of the {@link UserAccount} * @param start First record to retrieve * @param limit Number of records to retrieve * @return */ @Override public List<InboxView> findInbox(String username, int start, int limit) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("user", ua).and("permission", WorkflowStatePermission.GROUP).parameters(); return daoService.findWithNamedQuery(NewsItem.VIEW_INBOX, params, start, limit); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return Collections.EMPTY_LIST; } } /** * Gets items in the inbox for a particular user. * <p/> * @param username Username of the {@link UserAccount} * @return {@link List} of inbox items */ @Override public List<InboxView> findInbox(String username) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("user", ua).and("permission", WorkflowStatePermission.GROUP).parameters(); return daoService.findWithNamedQuery(NewsItem.VIEW_INBOX, params); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return Collections.EMPTY_LIST; } } /** * {@inheritDoc } */ @Override public List<NewsItem> findByActiveUser(String username) { List<NewsItem> items = new ArrayList<NewsItem>(); try { UserAccount ua = userFacade.findById(username); // Go through each privileged outlet List<Outlet> outlets = ua.getPrivilegedOutlets(); for (Outlet outlet : outlets) { // Look at each story in the outlet Map params = QueryBuilder.with("outlet", outlet).parameters(); List<NewsItem> itemsInOutlet = daoService.findWithNamedQuery(NewsItem.FIND_BY_OUTLET, params); for (NewsItem ni : itemsInOutlet) { WorkflowState state = ni.getCurrentState(); // Ignore completed and trashed items if (!state.isWorkflowTrash() && !state.isWorkflowEnd()) { // Check if the NewsItem is currently open for a group or // for a group attached to the story switch (state.getPermission()) { case GROUP: if (ua.getUserRoles().contains(state. getActorRole())) { items.add(ni); } break; case USER: // Check all the actors for the news item for (NewsItemActor actor : ni.getActors()) { // If the actor has the role of the state, he is a current actor if (actor.getRole().equals(state. getActorRole()) && actor.getUser(). equals(ua)) { items.add(ni); } } break; } } } } } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Unknown user", ex); } return items; } @Override public List<InboxView> findOutletBox(String username, Outlet outlet) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("outlet", outlet).and("user", ua). parameters(); return daoService.findWithNamedQuery(NewsItem.VIEW_OUTLET_BOX, params); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Could not find user {0}", username); return Collections.EMPTY_LIST; } } @Override public List<InboxView> findOutletBox(String username, Outlet outlet, WorkflowState state) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("outlet", outlet).and("state", state). and("user", ua).parameters(); return daoService.findWithNamedQuery(NewsItem.VIEW_OUTLET_BOX_STATE, params); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Could not find user {0}", username); return Collections.EMPTY_LIST; } } @Override public List<InboxView> findOutletBox(String username, Outlet outlet, WorkflowState state, int start, int results) { try { UserAccount ua = userFacade.findById(username); Map params = QueryBuilder.with("outlet", outlet).and("state", state). and("user", ua).parameters(); return daoService.findWithNamedQuery(NewsItem.VIEW_OUTLET_BOX_STATE, params, start, results); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Could not find user {0}", username); return Collections.EMPTY_LIST; } } /** * Permanently remove {@link NewsItem}s in the trash state by a given user. * * @param username Username of the {@link UserAccount} * @return Number of {@link NewsItem}s deleted */ @Override public int emptyTrash(String username) { int deleted = 0; try { UserAccount ua = userFacade.findById(username); Map<String, Object> params = QueryBuilder.with("user", ua). parameters(); List<NewsItem> newsItems = daoService.findWithNamedQuery( NewsItem.FIND_TRASH, params); for (NewsItem item : newsItems) { if (isOriginalOwner(item, username)) { deleteNewsItem(item.getId(), false); deleted++; } } } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Unknown user #{0}", new Object[]{username}); LOG.log(Level.FINEST, "", ex); } return deleted; } /** * Determine if the given user is the original owner of the given * {@link NewsItem}. * <p/> * @param item {@link NewsItem} to check * @param username Username of the {@link UserAccount} to check * @return {@code true} if {@code username} is the original owner of * {@code item} */ private boolean isOriginalOwner(NewsItem item, String username) { WorkflowState start = item.getOutlet().getWorkflow().getStartState(); UserRole role = start.getActorRole(); UserAccount user; try { user = userService.findById(username); } catch (Exception ex) { return false; } for (NewsItemActor nia : item.getActors()) { if (nia.getRole().equals(role) && nia.getUser().equals(user)) { return true; } } return false; } /** * {@inheritDoc } */ @Override public List<NewsItem> findByStateAndOutlet(WorkflowState state, Outlet outlet) { Map params = QueryBuilder.with("outlet", outlet).and("state", state). parameters(); return daoService.findWithNamedQuery(NewsItem.FIND_BY_OUTLET_AND_STATE, params); } /** * {@inheritDoc } */ @Override public List<NewsItem> findByStateAndOutlet(String stateName, Outlet outlet) { Map params = QueryBuilder.with("outlet", outlet).and("stateName", stateName).parameters(); return daoService.findWithNamedQuery( NewsItem.FIND_BY_OUTLET_AND_STATE_NAME, params); } /** * {@inheritDoc } */ @Override public List<NewsItem> findByStateAndOutlet(WorkflowState state, Outlet outlet, int start, int results) { Map params = QueryBuilder.with("outlet", outlet).and("state", state). parameters(); return daoService.findWithNamedQuery(NewsItem.FIND_BY_OUTLET_AND_STATE, params, start, results); } /** * {@inheritDoc } */ @Override public boolean isNewsItemPublished(final Long newsItemId) throws DataNotFoundException { NewsItem newsItem = findNewsItemById(newsItemId); if (newsItem.getCurrentState().equals(newsItem.getOutlet().getWorkflow(). getEndState())) { return true; } else { return false; } } /** * {@inheritDoc } */ @Override public List<NewsItem> findAssignmentsByOutlet(Outlet selectedOutlet) { Map<String, Object> params = QueryBuilder.with("outlet", selectedOutlet). parameters(); return daoService.findWithNamedQuery(NewsItem.FIND_ASSIGNMENTS_BY_OUTLET, params); } /** * {@inheritDoc } */ @Override public boolean deleteNewsItem(Long id) { try { NewsItemHolder checkout = checkout(id); Workflow workflow = checkout.getNewsItem().getOutlet().getWorkflow(); WorkflowState trashState = workflow.getTrashState(); step(checkout.getNewsItem(), trashState, ""); return true; } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Could not find news item to delete. {0}",ex.getMessage()); LOG.log(Level.FINE, "", ex); return false; } catch (WorkflowStateTransitionException ex) { LOG.log(Level.WARNING, "Could not delete news item. {0}", ex.getMessage()); LOG.log(Level.FINE, "", ex); return false; } } /** * {@inheritDoc } */ @Override public boolean deleteNewsItem(Long id, boolean safe) { if (safe) { return deleteNewsItem(id); } else { try { NewsItem item = daoService.findById(NewsItem.class, id); daoService.delete(NewsItem.class, id); // Schedule removal of news item from search engine searchEngine.addToIndexQueue(QueueEntryType.NEWS_ITEM, id, QueueEntryOperation.REMOVE); return true; } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Could not find news item to delete. {0}", ex.getMessage()); LOG.log(Level.FINE, "", ex); return false; } } } /** * {@inheritDoc} */ @Override public NewsItemActor addActorToNewsItem(NewsItemActor actor) { return daoService.create(actor); } /** * {@inheritDoc} */ @Override public NewsItem removeActorFromNewsItem(NewsItemActor actor) { Long newsItemId = actor.getNewsItem().getId(); daoService.delete(NewsItemActor.class, actor.getId()); try { return daoService.findById(NewsItem.class, newsItemId); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return null; } } /** * {@inheritDoc} */ @Override public ContentItemPermission getPermission(Long newsItemId, String username) { try { NewsItem item = findNewsItemById(newsItemId); UserAccount user = userFacade.findById(username); return getPermission(item, user); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return ContentItemPermission.UNAUTHORIZED; } } private ContentItemPermission getPermission(NewsItem item, UserAccount user) { UserRole currentRole = item.getCurrentState().getActorRole(); // Super users always have access if (user.isPrivileged(SystemPrivilege.SUPER_USER)) { return ContentItemPermission.USER; } if (item.getCurrentState().isGroupPermission()) { // NewsItem has been sent to a group - check if the user is part // of the group if (user.getUserRoles().contains(currentRole)) { return ContentItemPermission.ROLE; } } else { // NewsItem has been sent to selected users, check if the user // is among the selected users for (NewsItemActor actor : item.getActors()) { if (actor.getUser().equals(user) && actor.getRole().equals( currentRole)) { return ContentItemPermission.USER; } } } // If user is neither in the current role or among the current // actors, check if the user is among the other actors for (NewsItemActor actor : item.getActors()) { if (actor.getUser().equals(user)) { return ContentItemPermission.ACTOR; } } // If the user originally assigned the story, he should have access like // an actor. if (item.isAssigned() && item.getAssignedBy().equals(user)) { return ContentItemPermission.USER; } // If the user is an outlet planner, he has access to the story as an // actor if (user.isPrivileged(SystemPrivilege.OUTLET_PLANNING)) { return ContentItemPermission.ACTOR; } return ContentItemPermission.UNAUTHORIZED; } /** * {@inheritDoc} */ @Override public List<NewsItem> findVersions(Long newsItemId) { try { NewsItem newsItem = daoService.findById(NewsItem.class, newsItemId); return findVersions(newsItem); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return Collections.EMPTY_LIST; } } private List<NewsItem> findVersions(NewsItem newsItem) { Map<String, Object> parameters = QueryBuilder.with("newsItem", newsItem). parameters(); return daoService.findWithNamedQuery(NewsItem.FIND_VERSIONS, parameters); } /** * {@inheritDoc } */ @Override public NewsItem save(NewsItem newsItem) throws LockingException { try { Calendar now = Calendar.getInstance(); newsItem.setUpdated(now); newsItem.setPrecalculatedWordCount(newsItem.getWordCount()); newsItem.setPrecalculatedCurrentActor(newsItem.getCurrentActor()); return daoService.update(newsItem); } catch (Throwable t) { throw new LockingException(t); } } /** * {@inheritDoc } */ @Override public NewsItem findNewsItemById(Long id) throws DataNotFoundException { return daoService.findById(NewsItem.class, id); } @Override public NewsItemPlacement findNewsItemPlacementById(Long id) throws DataNotFoundException { return daoService.findById(NewsItemPlacement.class, id); } /** * {@inheritDoc } */ @Override public boolean isCheckedOut(Long id) { return !daoService.findWithNamedQuery(NewsItem.FIND_CHECKED_IN_NEWS_ITEM, QueryBuilder.with("id", id).parameters()).isEmpty(); } @Override public NewsItem checkin(NewsItem newsItem) throws LockingException { try { UserAccount updaterUser = userService.findById(ctx.getCallerPrincipal().getName()); return checkin(newsItem, updaterUser); } catch (UserNotFoundException ex) { LOG.log(Level.WARNING, "Unknown user", ex); throw new LockingException(ex); } catch (DirectoryException ex) { LOG.log(Level.WARNING, "Could not connect to directory", ex); throw new LockingException(ex); } } @Override public NewsItem checkin(NewsItem newsItem, UserAccount updaterUser) throws LockingException { try { NewsItem orig = daoService.findById(NewsItem.class, newsItem.getId()); if (!orig.isLocked()) { throw new LockingException("News Item #" + newsItem.getId() + " is not checked-out and can therefore not be checked-in"); } else if (orig.isLocked() && !orig.getCheckedOutBy().equals(updaterUser)) { throw new LockingException("News Item #" + newsItem.getId() + " is checked-out by another user: " + orig.getCheckedOutBy()); } newsItem.setUpdated(Calendar.getInstance()); newsItem.setPrecalculatedWordCount(newsItem.getWordCount()); newsItem.setPrecalculatedCurrentActor(newsItem.getCurrentActor()); newsItem.setCheckedOut(null); newsItem.setCheckedOutBy(null); return daoService.update(newsItem); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Unknown entity", ex); throw new LockingException(ex); } } /** * Checks-out a {@link NewsItem} from the database. * * @param id Unique identifier of the {@link NewsItem} * @return Checked-out {@link NewsItem} in a {@link NewsItemHolder} matching * the given {@code id} * @throws DataNotFoundException If no {@link NewsItem} could be found with * the given {@code id} */ @Override public NewsItemHolder checkout(Long id) throws DataNotFoundException { UserAccount user = null; try { user = userService.findById(ctx.getCallerPrincipal().getName()); pluginContext.setCurrentUserAccount(user); } catch (UserNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); } catch (DirectoryException ex) { LOG.log(Level.SEVERE, null, ex); } return checkout(id, user); } /** * Checks-out a {@link NewsItem} from the database. * * @param id Unique identifier of the {@link NewsItem} * @param user * @return Checked-out {@link NewsItem} in a {@link NewsItemHolder} matching * the given {@code id} * @throws DataNotFoundException If no {@link NewsItem} could be found with * the given {@code id} */ @Override public NewsItemHolder checkout(Long id, UserAccount user) throws DataNotFoundException { LOG.log(Level.INFO, "Checking out news item #{0}", id); boolean checkedOut = false; boolean readOnly = false; boolean pullbackAvailable = false; NewsItem newsItem = daoService.findById(NewsItem.class, id); ContentItemPermission permission = getPermission(newsItem, user); LOG.log(Level.INFO, "Permission of #{0} for {1} is {2}", new Object[]{id, user, permission.toString()}); if (newsItem.isLocked() && !newsItem.getCheckedOutBy().equals(user)) { // The item has been checked out and the check-out user is not the same as the one who has already checked it out LOG.log(Level.INFO, "News Item #{0} is locked by {1}", new Object[]{id, newsItem.getCheckedOutBy()}); pluginContext.log(LogSeverity.INFO, "News Item #{0} is locked by {1}", new Object[]{id, newsItem.getCheckedOutBy().getFullName()}, newsItem, id); checkedOut = false; readOnly = true; } else if (newsItem.isLocked() && newsItem.getCheckedOutBy().equals(user)) { LOG.log(Level.INFO, "News Item #{0} is already locked by {1}", new Object[]{id, newsItem.getCheckedOutBy()}); pluginContext.log(LogSeverity.INFO, "News Item #{0} is already locked by {1}", new Object[]{id, newsItem.getCheckedOutBy().getFullName()}, newsItem, id); // The item has been checked out but the same user asking to check it out again checkedOut = true; readOnly = false; } else { LOG.log(Level.INFO, "News Item #{0} is not locked", new Object[]{id}); if (permission == ContentItemPermission.USER || permission == ContentItemPermission.ROLE) { pluginContext.log(LogSeverity.INFO, "Locking News Item #{0} for {1}", new Object[]{id, user.getFullName()}, newsItem, id); LOG.log(Level.INFO, "Locking News Item #{0} for {1}", new Object[]{id, user}); // Check-out user is the same as the current user or role of the content item newsItem.setCheckedOut(Calendar.getInstance()); newsItem.setCheckedOutBy(user); newsItem = daoService.update(newsItem); checkedOut = true; readOnly = false; } else { // Check-out user is an actor of the content item, but not the current actor LOG.log(Level.INFO, "Read-only access obtained to News Item #{0} for {1}. News Item was not checked-out", new Object[]{id, user.getFullName()}); checkedOut = false; readOnly = true; } } List<NewsItem> versions = findVersions(newsItem); // Determine if pullback is available if (newsItem.getLatestTransition().getUser().equals(user) && newsItem. getCurrentState().isPullbackEnabled()) { pullbackAvailable = true; } // Determine which fields should be visible Map<String, Boolean> fieldVisibility = new HashMap<String, Boolean>(); List<NewsItemFieldVisible> visibleFields = newsItem.getCurrentState(). getVisibleFields(); for (NewsItemField field : NewsItemField.values()) { // Assume that the field is not visible fieldVisibility.put(field.name(), Boolean.FALSE); // Go through visible fields and check if it should be visible for (NewsItemFieldVisible visibleField : visibleFields) { if (visibleField.getField() == field) { fieldVisibility.put(field.name(), Boolean.TRUE); break; } } } return new NewsItemHolder(newsItem, versions, permission, readOnly, checkedOut, pullbackAvailable, fieldVisibility); } /** * {@inheritDoc } */ @Override public boolean revokeLock(final Long id) { int affected = daoService.executeQuery(NewsItem.REVOKE_LOCK, QueryBuilder.with("id", id)); if (affected > 0) { return true; } else { return false; } } /** * {@inheritDoc } */ @Override public int revokeLocks(final String username) { try { UserAccount ua = userService.findById(username); return daoService.executeQuery(NewsItem.REVOKE_LOCKS, QueryBuilder. with("user", ua)); } catch (UserNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); return 0; } catch (DirectoryException ex) { LOG.log(Level.SEVERE, null, ex); return 0; } } /** * {@inheritDoc } */ @Override public int revokeAllLocks() { return daoService.executeQuery(NewsItem.REVOKE_ALL_LOCKS); } /** * {@inheritDoc } */ @Override public void pullback(Long id) throws LockingException, WorkflowStateTransitionException { LOG.log(Level.INFO, "Pulling back news item #{0}", new Object[]{id}); try { NewsItemHolder nih = checkout(id); NewsItem ni = nih.getNewsItem(); if (ni.isLocked()) { throw new LockingException( "News item is locked and can't be pulled back"); } if (!ni.getCurrentState().isPullbackEnabled()) { throw new WorkflowStateTransitionException(ni.getCurrentState(). getName() + " does not support pullback"); } if (ni.getHistory().size() < 2) { throw new WorkflowStateTransitionException( "Not enough history to pullback"); } String uid = ctx.getCallerPrincipal().getName(); UserAccount ua = null; try { ua = userService.findById(uid); } catch (Exception ex) { throw new WorkflowStateTransitionException( "Could not resolve initator", ex); } Calendar now = Calendar.getInstance(); Collections.sort(ni.getHistory(), new BeanComparator("timestamp", false)); WorkflowStateTransition oldTransition = ni.getHistory().get(1); WorkflowState nextState = daoService.findById(WorkflowState.class, oldTransition.getState().getId()); WorkflowStateTransition transition = new WorkflowStateTransition(ni, now, nextState, ua); transition.setStoryVersion(ni.getStory()); transition.setHeadlineVersion(ni.getTitle()); transition.setBriefVersion(ni.getBrief()); transition.setComment(""); ni.setCurrentState(nextState); ni.getHistory().add(transition); ni.setUpdated(now); try { if (nih.getPermission() == ContentItemPermission.USER) { ni = checkin(ni); } else { ni = save(ni); } } catch (LockingException ex) { throw new WorkflowStateTransitionException(ex); } } catch (DataNotFoundException ex) { throw new LockingException(ex); } } /** * {@inheritDoc } */ @Override public MediaItem create(MediaItem mediaItem) { mediaItem.setCreated(java.util.Calendar.getInstance()); return daoService.create(mediaItem); } /** * {@inheritDoc } */ @Override public NewsItemMediaAttachment create(NewsItemMediaAttachment attachment) { return daoService.create(attachment); } /** * {@inheritDoc } */ @Override public NewsItemMediaAttachment update(NewsItemMediaAttachment attachment) { return daoService.update(attachment); } /** * {@inheritDoc } */ @Override public void deleteMediaAttachmentById(Long id) { daoService.delete(NewsItemMediaAttachment.class, id); } /** * {@inheritDoc } */ @Override public NewsItem findNewsItemFromArchive(Long id) throws DataNotFoundException { NewsItem ni = findNewsItemById(id); if (ni.getCurrentState().equals( ni.getOutlet().getWorkflow().getEndState())) { return ni; } else { throw new DataNotFoundException(); } } @Override public NewsItemPlacement addToNextEdition(NewsItem newsItem, Section section) throws DataNotFoundException { Edition nextEdition = outletFacade.findNextEdition(newsItem.getOutlet()); if (nextEdition.getId() == null) { nextEdition = outletFacade.createEdition(nextEdition); } NewsItemPlacement placement = new NewsItemPlacement(); placement.setEdition(nextEdition); placement.setSection(section); placement.setOutlet(newsItem.getOutlet()); placement.setNewsItem(newsItem); return daoService.create(placement); } @Override public NewsItemPlacement createPlacement(NewsItemPlacement placement) { return daoService.create(placement); } @Override public NewsItemPlacement updatePlacement(NewsItemPlacement placement) { try { return daoService.update(placement); } catch (OptimisticLockException ex) { LOG.log(Level.WARNING, "OptimsticLockException occured upon updating the NewsItemPlacement #{0}", new Object[]{placement.getId()}); return placement; } } @Override public NewsItemPlacement updatePlacement(Long placementId, Integer start, Integer position) { try { NewsItemPlacement nip; nip = daoService.findById(NewsItemPlacement.class, placementId); nip.setStart(start); nip.setPosition(position); return daoService.update(nip); } catch (DataNotFoundException ex) { return null; } } @Override public void deletePlacement(NewsItemPlacement placement) { deletePlacementById(placement.getId()); } @Override public void deletePlacementById(Long id) { daoService.delete(NewsItemPlacement.class, id); } @Override public void updatePrecalculatedFields() { for (NewsItem ni : daoService.findAll(NewsItem.class)) { ni.setPrecalculatedCurrentActor(ni.getCurrentActor()); ni.setPrecalculatedWordCount(ni.getWordCount()); } } @Override public NewsItemEditionState addNewsItemEditionState(Long editionId, Long newsItemId, String property, String value) { try { Edition edition = outletFacade.findEditionById(editionId); NewsItem newsitem = findNewsItemById(newsItemId); NewsItemEditionState editionState = new NewsItemEditionState(edition, newsitem, "", property, value, false); return daoService.create(editionState); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, ex.getMessage()); LOG.log(Level.FINEST, "", ex); } return new NewsItemEditionState(); } @Override public NewsItemEditionState updateNewsItemEditionState(NewsItemEditionState newsItemEditionState) { return daoService.update(newsItemEditionState); } @Override public NewsItemEditionState findNewsItemEditionState(Long editionId, Long newsItemId, String property) throws DataNotFoundException { QueryBuilder criteria = QueryBuilder.with(NewsItemEditionState.PARAM_EDITION_ID, editionId).and(NewsItemEditionState.PARAM_NEWS_ITEM_ID, newsItemId).and(NewsItemEditionState.PARAM_PROPERTY, property); return daoService.findObjectWithNamedQuery(NewsItemEditionState.class, NewsItemEditionState.FIND_BY_EDITION_NEWSITEM_PROPERTY, criteria.parameters()); } @Override public NewsItemEditionState findNewsItemEditionStateOrCreate(Long editionId, Long newsItemId, String property, String value) { LOG.log(Level.FINER, "Looking for NewsItemEditionState. Edition: {0}, NewsItemId: {1}, Property:{2}", new Object[]{editionId, newsItemId, property}); NewsItemEditionState state; try { state = findNewsItemEditionState(editionId, newsItemId, property); LOG.log(Level.FINER, "NewsItemEditionState found: {0}", state.getId()); } catch (DataNotFoundException ex) { LOG.log(Level.FINER, "NewsItemEditionState was not found. Creating new NewsItemEditionState"); state = addNewsItemEditionState(editionId, newsItemId, property, value); } return state; } @Override public List<NewsItemEditionState> findNewsItemEditionStates(Long editionId, Long newsItemId) { QueryBuilder criteria = QueryBuilder .with(NewsItemEditionState.PARAM_EDITION_ID, editionId) .and(NewsItemEditionState.PARAM_NEWS_ITEM_ID, newsItemId); return daoService.findWithNamedQuery(NewsItemEditionState.FIND_BY_EDITION_NEWSITEM, criteria.parameters()); } @Override public List<NewsItemEditionState> findNewsItemEditionStates(Long editionId) { QueryBuilder criteria = QueryBuilder .with(NewsItemEditionState.PARAM_EDITION_ID, editionId); return daoService.findWithNamedQuery(NewsItemEditionState.FIND_BY_EDITION, criteria.parameters()); } @Override public void clearNewsItemEditionStateById(Long newsItemEditionStateId) { daoService.delete(NewsItemEditionState.class, newsItemEditionStateId); } @Override public void clearNewsItemEditionState(Long editionId) { QueryBuilder criteria = QueryBuilder.with(NewsItemEditionState.PARAM_EDITION_ID, editionId); daoService.executeQuery(NewsItemEditionState.DELETE_BY_EDITION, criteria); } @Override public void clearNewsItemEditionState(Long editionId, Long newsItemId) { QueryBuilder criteria = QueryBuilder.with(NewsItemEditionState.PARAM_EDITION_ID, editionId).and(NewsItemEditionState.PARAM_NEWS_ITEM_ID, newsItemId); daoService.executeQuery(NewsItemEditionState.DELETE_BY_EDITION_NEWSITEM, criteria); } @Override public NewsItemActionState addNewsItemActionState(Long editionId, Long newsItemId, String action, String state, String data) { try { Edition edition = outletFacade.findEditionById(editionId); NewsItem newsitem = findNewsItemById(newsItemId); NewsItemActionState newsItemActionState = new NewsItemActionState(edition, newsitem, action, state, data); return daoService.create(newsItemActionState); } catch (DataNotFoundException ex) { LOG.log(Level.SEVERE, ex.getMessage()); LOG.log(Level.FINEST, "", ex); } return new NewsItemActionState(); } @Override public NewsItemActionState updateNewsItemActionState(NewsItemActionState newsItemActionState) { return daoService.update(newsItemActionState); } @Override public NewsItemActionState findNewsItemActionState(Long editionId, Long newsItemId, String action) throws DataNotFoundException { LOG.log(Level.FINER, "Looking for NewsItemActionState. Edition: {0}, NewsItem: {1}, Action: {2}", new Object[]{editionId, newsItemId, action}); QueryBuilder criteria = QueryBuilder .with(NewsItemActionState.PARAM_EDITION_ID, editionId) .and(NewsItemActionState.PARAM_NEWS_ITEM_ID, newsItemId) .and(NewsItemActionState.PARAM_ACTION, action); return daoService.findObjectWithNamedQuery(NewsItemActionState.class, NewsItemActionState.FIND_BY_EDITION_NEWSITEM_ACTION, criteria.parameters()); } @Override public NewsItemActionState findNewsItemActionStateOrCreate(Long editionId, Long newsItemId, String action, String state, String data) { NewsItemActionState newsItemActionState; try { newsItemActionState = findNewsItemActionState(editionId, newsItemId, action); LOG.log(Level.FINER, "NewsItemActionState found: {0}", newsItemActionState.getId()); } catch (DataNotFoundException ex) { LOG.log(Level.FINER, "NewsItemActionState was not found. Creating new NewsItemActionState"); newsItemActionState = addNewsItemActionState(editionId, newsItemId, action, state, data); } return newsItemActionState; } @Override public List<NewsItemActionState> findNewsItemActionStates(Long editionId) { QueryBuilder criteria = QueryBuilder.with(NewsItemActionState.PARAM_EDITION_ID, editionId); return daoService.findWithNamedQuery(NewsItemActionState.FIND_BY_EDITION, criteria.parameters()); } @Override public List<NewsItemActionState> findNewsItemActionStates(Long editionId, Long newsItemId) { QueryBuilder criteria = QueryBuilder .with(NewsItemActionState.PARAM_EDITION_ID, editionId) .and(NewsItemActionState.PARAM_NEWS_ITEM_ID, newsItemId); return daoService.findWithNamedQuery(NewsItemActionState.FIND_BY_EDITION_NEWSITEM, criteria.parameters()); } @Override public void clearNewsItemActionStateById(Long newsItemActionStateId) { daoService.delete(NewsItemActionState.class, newsItemActionStateId); } @Override public void clearNewsItemActionState(Long editionId) { QueryBuilder criteria = QueryBuilder.with(NewsItemActionState.PARAM_EDITION_ID, editionId); daoService.executeQuery(NewsItemActionState.DELETE_BY_EDITION, criteria); } @Override public void clearNewsItemActionState(Long editionId, Long newsItemId) { QueryBuilder criteria = QueryBuilder .with(NewsItemActionState.PARAM_EDITION_ID, editionId) .and(NewsItemActionState.PARAM_NEWS_ITEM_ID, newsItemId); daoService.executeQuery(NewsItemActionState.DELETE_BY_EDITION_NEWSITEM, criteria); } /** * Helper function for getting the current workflow user. * * @return {@link UserAccount} matching to currently user logged in to the * EJB context. * @throws WorkflowStateTransitionException If the user could not be found * in the database or directory */ private UserAccount getCurrentWorkflowUser() throws WorkflowStateTransitionException { String uid = ctx.getCallerPrincipal().getName(); try { UserAccount userAccount = userService.findById(uid); pluginContext.setCurrentUserAccount(userAccount); return userAccount; } catch (UserNotFoundException ex) { throw new WorkflowStateTransitionException("Could not resolve transition initator as the user " + uid + " was not found in the database.", ex); } catch (DirectoryException ex) { throw new WorkflowStateTransitionException("Could not resolve transition initator. " + ex.getMessage(), ex); } } /** * Helper function for executing all the {@link WorkflowStepAction}s for a * {@link WorkflowStep}. * * @param newsItem {@link NewsItem} for which to execute the * {@link WorkflowStepAction}s * @param workflowStep {@link WorkflowStep} containing the * {@link WorkflowStepAction}s * @param actor {@link UserAccount} initiating the * {@link WorkflowStepAction}s */ private void executeWorkflowStepActions(NewsItem newsItem, WorkflowStep workflowStep, UserAccount actor) { pluginContext.log(LogSeverity.FINE, "Executing workflow step actions", newsItem, newsItem.getId()); for (WorkflowStepAction action : workflowStep.getActions()) { try { WorkflowAction workflowAction = action.getAction(); workflowAction.execute(pluginContext, newsItem, action, actor); } catch (WorkflowActionException ex) { pluginContext.log(LogSeverity.SEVERE, "Could not execute action {0}. Reason: {1}", new Object[]{action.getLabel(), ex.getMessage()}, newsItem, newsItem.getId()); LOG.log(Level.FINEST, "", ex); } } } }