/*
* Copyright (C) 2010 - 2012 Interactive Media Management
*
* 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.jsf.beans;
import dk.i2m.converge.core.DataNotFoundException;
import dk.i2m.converge.core.content.AssignmentType;
import dk.i2m.converge.core.content.NewsItem;
import dk.i2m.converge.core.content.NewsItemActor;
import dk.i2m.converge.core.content.NewsItemPlacement;
import dk.i2m.converge.core.content.catalogue.Catalogue;
import dk.i2m.converge.core.content.catalogue.MediaItem;
import dk.i2m.converge.core.content.catalogue.MediaItemStatus;
import dk.i2m.converge.core.security.SystemPrivilege;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.core.views.InboxView;
import dk.i2m.converge.core.workflow.Outlet;
import dk.i2m.converge.core.workflow.WorkflowState;
import dk.i2m.converge.ejb.facades.CatalogueFacadeLocal;
import dk.i2m.converge.ejb.facades.DuplicateExecutionException;
import dk.i2m.converge.ejb.facades.NewsItemFacadeLocal;
import dk.i2m.converge.core.workflow.WorkflowStateTransitionException;
import dk.i2m.converge.jsf.components.tags.DialogSelfAssignment;
import dk.i2m.jsf.JsfUtils;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.event.ActionEvent;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import org.richfaces.component.html.HtmlTree;
import org.richfaces.event.NodeSelectedEvent;
import org.richfaces.model.TreeNode;
import org.richfaces.model.TreeNodeImpl;
/**
* Backing bean for {@code /Inbox.jspx}.
*
* @author Allan Lykke Christensen
*/
public class Inbox {
private static final Logger LOG = Logger.getLogger(Inbox.class.getName());
/** ResourceBundle containing internationalised messages. */
private ResourceBundle bundle = JsfUtils.getResourceBundle("i18n");
@EJB private NewsItemFacadeLocal newsItemFacade;
@EJB private CatalogueFacadeLocal catalogueFacade;
private NewsItem selectedNewsItem;
private DataModel newsItems = null;
private DataModel mediaItems = null;
private TreeNode outletsNode = null;
private String inboxTitle = "";
private NewsItem duplicateNewsItem;
private DialogSelfAssignment newAssignment;
private boolean showNewsItem = true;
private boolean catalogueEditor = false;
private String newAssignmentType = "tabStory";
private String createdItemLink;
private Long selectedItemForTrashing;
/**
* Creates a new instance of {@link Inbox}.
*/
public Inbox() {
}
@PostConstruct
public void onInit() {
onShowMyAssignments(null);
}
/**
* Gets the title of the Inbox. The title changes depending on the selected
* folder/state.
*
* @return Title of the inbox
*/
public String getInboxTitle() {
return JsfUtils.getMessage("i18n", "Inbox_INBOX_X", new Object[]{
inboxTitle});
}
public String getNewAssignmentType() {
return newAssignmentType;
}
public void setNewAssignmentType(String newAssignmentType) {
this.newAssignmentType = newAssignmentType;
}
/**
* Gets a {@link DataModel} containing the user's {@link NewsItem}s.
*
* @return {@link DataModel} containing the user's {@link NewsItem}s
*/
public DataModel getNewsItems() {
if (newsItems == null) {
newsItems = new ListDataModel(new ArrayList());
}
return newsItems;
}
public DataModel getMediaItems() {
if (mediaItems == null) {
mediaItems = new ListDataModel(new ArrayList());
}
return mediaItems;
}
/**
* Action listeners for preparing the creation of a new assignment.
*
* @param event
* {@link ActionEvent} that invoked the listener.
*/
public void onNewAssignment(ActionEvent event) {
try {
switch (getUser().getDefaultAssignmentType()) {
case MEDIA_ITEM:
this.newAssignmentType = "tabMedia";
break;
case NEWS_ITEM:
this.newAssignmentType = "tabStory";
break;
}
} catch (NullPointerException ex) {
// Default assignment type not set
this.newAssignmentType = "tabStory";
}
newAssignment = new DialogSelfAssignment();
newAssignment.getAssignment().setDeadline(
java.util.Calendar.getInstance());
newAssignment.getAssignment().getDeadline().setTimeZone(getUser().
getTimeZone());
if (newAssignment.getAssignment().getDeadline().get(
java.util.Calendar.HOUR_OF_DAY) >= 15) {
newAssignment.getAssignment().getDeadline().add(
java.util.Calendar.DAY_OF_MONTH, 1);
}
newAssignment.getAssignment().getDeadline().set(
java.util.Calendar.HOUR_OF_DAY, 15);
newAssignment.getAssignment().getDeadline().set(
java.util.Calendar.MINUTE, 0);
newAssignment.getAssignment().getDeadline().set(
java.util.Calendar.SECOND, 0);
newAssignment.getNewsItem().setOutlet(getUser().getDefaultOutlet());
if (newAssignment.getNewsItem().getOutlet() != null) {
newAssignment.getNewsItem().setLanguage(newAssignment.getNewsItem().
getOutlet().getLanguage());
}
newAssignment.getAssignment().setType(
getUser().getDefaultAssignmentType());
newAssignment.getMediaItem().setCatalogue(getUser().
getDefaultMediaRepository());
newAssignment.setNextEdition(getUser().isDefaultAddNextEdition());
}
/**
* Event handler for creating a new assignment.
* <p/>
* @param event Event that invoked the handler
*/
public void onAddAssignment(ActionEvent event) {
if (newAssignmentType.equalsIgnoreCase("tabStory")) {
newAssignment.getAssignment().setType(AssignmentType.NEWS_ITEM);
} else {
newAssignment.getAssignment().setType(AssignmentType.MEDIA_ITEM);
}
switch (newAssignment.getAssignment().getType()) {
case NEWS_ITEM:
if (newAssignment.getNewsItem().getOutlet() == null) {
JsfUtils.createMessage("frmInbox",
FacesMessage.SEVERITY_ERROR, "i18n",
"Inbox_NEWS_ITEM_OUTLET_REQUIRED", new Object[]{});
return;
}
try {
selectedNewsItem = newAssignment.getNewsItem();
NewsItemActor nia = new NewsItemActor();
nia.setRole(selectedNewsItem.getOutlet().getWorkflow().
getStartState().getActorRole());
nia.setUser(getUser());
nia.setNewsItem(selectedNewsItem);
selectedNewsItem.getActors().add(nia);
selectedNewsItem.setDeadline(newAssignment.getAssignment().
getDeadline());
if (selectedNewsItem.getOutlet() != null) {
selectedNewsItem.setLanguage(selectedNewsItem.getOutlet().
getLanguage());
}
selectedNewsItem.setTitle(newAssignment.getTitle());
selectedNewsItem = newsItemFacade.start(selectedNewsItem);
this.createdItemLink = "NewsItem.xhtml?id="
+ selectedNewsItem.getId();
if (newAssignment.isNextEdition()) {
try {
NewsItemPlacement placement = newsItemFacade.
addToNextEdition(selectedNewsItem, getUser().
getDefaultSection());
selectedNewsItem = placement.getNewsItem();
} catch (DataNotFoundException ex) {
LOG.log(Level.INFO, "Could not find next edition");
}
}
JsfUtils.createMessage("frmInbox",
FacesMessage.SEVERITY_INFO, Bundle.i18n.name(),
"Inbox_ASSIGNMENT_CREATED");
} catch (DuplicateExecutionException ex) {
// Double click prevention - stamp in log
LOG.log(Level.INFO, ex.getMessage());
} catch (WorkflowStateTransitionException ex) {
JsfUtils.createMessage("frmInbox",
FacesMessage.SEVERITY_ERROR, Bundle.i18n.name(),
"Inbox_ASSIGNMENT_CREATION_ERROR");
LOG.log(Level.SEVERE, ex.getMessage(), ex);
}
break;
case MEDIA_ITEM:
if (newAssignment.getMediaItem().getCatalogue() == null) {
JsfUtils.createMessage("frmInbox",
FacesMessage.SEVERITY_ERROR, Bundle.i18n.name(),
"Inbox_MEDIA_ITEM_CATELOGUE_REQUIRED");
return;
}
newAssignment.getMediaItem().setStatus(
MediaItemStatus.UNSUBMITTED);
newAssignment.getMediaItem().setTitle(newAssignment.getTitle());
newAssignment.getMediaItem().setOwner(getUser());
newAssignment.getMediaItem().setByLine(getUser().getFullName());
MediaItem newItem = catalogueFacade.create(newAssignment.
getMediaItem());
this.createdItemLink = "MediaItemDetails.xhtml?id=" + newItem.
getId();
JsfUtils.createMessage("frmInbox", FacesMessage.SEVERITY_INFO,
Bundle.i18n.name(), "Inbox_ASSIGNMENT_CREATED");
showNewsItem = false;
break;
}
}
public String getCreatedItemLink() {
return this.createdItemLink;
}
/**
* Action Listener for removing articles marked as deleted.
*
* @param event
* {@link ActionEvent} that invoked the listener
*/
public void onEmptyTrash(ActionEvent event) {
int deleted = newsItemFacade.emptyTrash(getUser().getUsername());
onShowMyAssignments(event);
JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO,
Bundle.i18n.name(), "Inbox_X_ITEMS_DELETED", new Object[]{
deleted});
}
/**
* Event handler for showing the current assignments of the user.
* <p/>
* @param event * Event that invoked the handler
*/
public void onShowMyAssignments(ActionEvent event) {
showNewsItem = true;
this.inboxTitle = bundle.getString("Inbox_MY_ASSIGNMENTS");
List<InboxView> inboxView = newsItemFacade.findInbox(getUser().
getUsername());
this.newsItems = new ListDataModel(inboxView);
}
public NewsItem getSelectedNewsItem() {
return selectedNewsItem;
}
public void setSelectedNewsItem(NewsItem selectedNewsItem) {
this.selectedNewsItem = selectedNewsItem;
}
public Long getSelectedItemForTrashing() {
return selectedItemForTrashing;
}
public void setSelectedItemForTrashing(Long selectedItemForTrashing) {
this.selectedItemForTrashing = selectedItemForTrashing;
newsItemFacade.deleteNewsItem(selectedItemForTrashing);
((Vector)getNewsItems().getWrappedData()).remove(getNewsItems().getRowData());
JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO,
Bundle.i18n.name(), "Inbox_ITEM_TRASHED", new Object[]{
((InboxView)getNewsItems().getRowData()).getId(),
((InboxView)getNewsItems().getRowData()).getTitle()});
}
public Map<String, Outlet> getPrivilegedOutlets() {
Map<String, Outlet> outlets = new LinkedHashMap<String, Outlet>();
for (Outlet outlet : getUser().getPrivilegedOutlets(
SystemPrivilege.MY_NEWS_ITEMS)) {
outlets.put(outlet.getTitle(), outlet);
}
return outlets;
}
public TreeNode getOutletsNode() {
if (outletsNode == null) {
outletsNode = new TreeNodeImpl();
List<Outlet> outlets = getUser().getPrivilegedOutlets(
SystemPrivilege.MY_NEWS_ITEMS);
for (Outlet outlet : outlets) {
if (outlet.isValid()) {
TreeNode node = new TreeNodeImpl();
node.setData(new OutletNode(outlet, null, outlet.getClass().
getName()));
List<WorkflowState> states =
outlet.getWorkflow().getStates();
for (WorkflowState state : states) {
TreeNode subNode = new TreeNodeImpl();
subNode.setData(new OutletNode(state, outlet, state.
getClass().getName()));
node.addChild(state.getId(), subNode);
}
outletsNode.addChild("O" + outlet.getId(), node);
}
}
List<Catalogue> myCatalogues = catalogueFacade.findCataloguesByUser(
getUser().getUsername());
for (Catalogue myCatalogue : myCatalogues) {
TreeNode node = new TreeNodeImpl();
node.setData(new OutletNode(myCatalogue, null, myCatalogue.
getClass().getName()));
for (MediaItemStatus status : MediaItemStatus.values()) {
TreeNode subNode = new TreeNodeImpl();
subNode.setData(new OutletNode(status, myCatalogue,
MediaItemStatus.class.getName()));
node.addChild(status.name(), subNode);
}
outletsNode.addChild("M" + myCatalogue.getId(), node);
}
}
return outletsNode;
}
/**
* Event handler for handling selection of outlet and catalogue folders.
*
* @param event
* Event that invoked the handler
*/
public void onOutletFolderSelect(NodeSelectedEvent event) {
this.catalogueEditor = false;
HtmlTree tree = (HtmlTree) event.getComponent();
OutletNode node = (OutletNode) tree.getRowData();
if (node.getData() instanceof Outlet) {
showNewsItem = true;
mediaItems = new ListDataModel();
Outlet outlet = (Outlet) node.getData();
inboxTitle = outlet.getTitle();
newsItems = new ListDataModel(newsItemFacade.findOutletBox(getUser().
getUsername(), outlet));
} else if (node.getData() instanceof WorkflowState) {
showNewsItem = true;
mediaItems = new ListDataModel();
WorkflowState state = (WorkflowState) node.getData();
Outlet outlet = (Outlet) node.getParentData();
inboxTitle = outlet.getTitle() + " - " + state.getName();
if (state.equals(outlet.getWorkflow().getEndState())) {
newsItems =
new ListDataModel(newsItemFacade.findOutletBox(getUser().
getUsername(), outlet, state, 0, 100));
} else {
newsItems =
new ListDataModel(newsItemFacade.findOutletBox(getUser().
getUsername(), outlet, state));
}
} else if (node.getData() instanceof Catalogue) {
showNewsItem = false;
newsItems = new ListDataModel();
Catalogue repository = (Catalogue) node.getData();
if (getUser().getUserRoles().contains(repository.getEditorRole())) {
this.catalogueEditor = true;
}
inboxTitle = repository.getName();
mediaItems =
new ListDataModel(catalogueFacade.findCurrentMediaItems(
getUser(), repository.getId()));
} else if (node.getData() instanceof MediaItemStatus) {
showNewsItem = false;
newsItems = new ListDataModel();
Catalogue catalogue = (Catalogue) node.getParentData();
if (getUser().getUserRoles().contains(catalogue.getEditorRole())) {
this.catalogueEditor = true;
}
MediaItemStatus status = (MediaItemStatus) node.getData();
String catalogueStatus = JsfUtils.getMessage(Bundle.i18n.name(),
"Generic_MEDIA_ITEM_STATUS_"
+ status.name(), new Object[]{});
inboxTitle = JsfUtils.getMessage(Bundle.i18n.name(),
"Inbox_CATALOGUE_STATUS", new Object[]{catalogue.getName(),
catalogueStatus});
mediaItems =
new ListDataModel(catalogueFacade.findCurrentMediaItems(
getUser(), status, catalogue.getId()));
}
}
/**
* Event handler for updating the state of an {@link MediaItem} from the
* list of {@link MediaItem}s in a {@link Catalogue} folder.
* <p/>
* @param item {@link MediaItem} to update
*/
public void setUpdateMediaItem(MediaItem item) {
// The media item must be removed and added to keep its version
// identifier current. If it is not updated in the datamodel, it will
// throw an internal server error upon the second update
// Remove media item from data model
((List<MediaItem>) getMediaItems().getWrappedData()).remove(item);
// Update the media item in the database
item = catalogueFacade.update(item);
// Add the media item back in the data model
((List<MediaItem>) getMediaItems().getWrappedData()).add(item);
}
public boolean isCatalogueEditor() {
return this.catalogueEditor;
}
public NewsItem getDuplicateNewsItem() {
return duplicateNewsItem;
}
public void setDuplicateNewsItem(NewsItem duplicateNewsItem) {
this.duplicateNewsItem = duplicateNewsItem;
if (this.duplicateNewsItem != null) {
onNewAssignment(null);
newAssignment.getNewsItem().setTitle(
getDuplicateNewsItem().getTitle());
newAssignment.setTitle(getDuplicateNewsItem().getTitle());
newAssignment.getNewsItem().setBrief(
getDuplicateNewsItem().getBrief());
newAssignment.getNewsItem().setStory(
getDuplicateNewsItem().getStory());
newAssignment.getNewsItem().setVersionOf(getDuplicateNewsItem());
}
}
public DialogSelfAssignment getNewAssignment() {
return newAssignment;
}
public void setNewAssignment(DialogSelfAssignment newAssignment) {
this.newAssignment = newAssignment;
}
private UserAccount getUser() {
final String valueExpression = "#{userSession.user}";
return (UserAccount) JsfUtils.getValueOfValueExpression(valueExpression);
}
public boolean isShowNewsItem() {
return showNewsItem;
}
public boolean isShowMediaItem() {
return !showNewsItem;
}
public class OutletNode {
private Object data;
private Object parentData;
private String type;
public OutletNode(Object data, Object parentData, String type) {
this.data = data;
this.parentData = parentData;
this.type = type;
}
public Object getParentData() {
return parentData;
}
public void setParentData(Object parentData) {
this.parentData = parentData;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final OutletNode other = (OutletNode) obj;
if (this.data != other.data && (this.data == null || !this.data.
equals(other.data))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + (this.data != null ? this.data.hashCode() : 0);
return hash;
}
}
public class MediaRepositoryNode {
private Object data;
private Object parentData;
private String type;
public MediaRepositoryNode(Object data, Object parentData, String type) {
this.data = data;
this.parentData = parentData;
this.type = type;
}
public Object getParentData() {
return parentData;
}
public void setParentData(Object parentData) {
this.parentData = parentData;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MediaRepositoryNode other = (MediaRepositoryNode) obj;
if (this.data != other.data && (this.data == null || !this.data.
equals(other.data))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + (this.data != null ? this.data.hashCode() : 0);
return hash;
}
}
}