/*
* (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library 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
* Lesser General Public License for more details.
*
* Contributors:
* Anahide Tchertchian
*/
package org.nuxeo.ecm.platform.ui.web.api;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.core.Events;
import org.nuxeo.ecm.platform.actions.Action;
import org.nuxeo.ecm.platform.actions.ActionContext;
import org.nuxeo.ecm.platform.actions.ejb.ActionManager;
/**
* Handles selected action tabs and raised events on current tab change.
*
* @see WebActions#CURRENT_TAB_CHANGED_EVENT
* @since 5.4.2
*/
public class TabActionsSelection implements Serializable {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(TabActionsSelection.class);
/**
* Map of current tab actions, with category as key and corresponding
* action as value.
* <p>
* Use a linked has map to preserve order when using several selections as
* sub tabs management needs order to be preserved.
*/
protected Map<String, Action> currentTabActions = new LinkedHashMap<String, Action>();
/**
* Returns the current action for given category.
*/
public Action getCurrentTabAction(String category) {
if (currentTabActions.containsKey(category)) {
return currentTabActions.get(category);
}
return null;
}
/**
* Sets the current action for given category, with additional sub tabs.
* <p>
* If given action is null, it resets the current action for this category.
*/
public void setCurrentTabAction(String category, Action tabAction) {
if (category == null) {
return;
}
if (tabAction != null) {
String[] actionCategories = tabAction.getCategories();
if (actionCategories != null) {
boolean categoryFound = false;
for (String actionCategory : actionCategories) {
if (category.equals(actionCategory)) {
categoryFound = true;
Action oldAction = currentTabActions.get(category);
currentTabActions.put(category, tabAction);
raiseEventOnCurrentTabSelected(category,
tabAction.getId());
if (oldAction == null
|| !oldAction.getId().equals(tabAction.getId())) {
// only raise the event if action actually changed
raiseEventOnCurrentTabChange(category,
tabAction.getId());
}
if (oldAction != null) {
// additional cleanup of possible sub tabs
resetCurrentTabs(getSubTabCategory(oldAction.getId()));
}
break;
}
}
if (!categoryFound) {
log.error(String.format(
"Cannot set current action '%s' for category"
+ " '%s' as this action does not "
+ "hold the given category.",
tabAction.getId(), category));
}
}
} else {
resetCurrentTabs(category);
}
}
public String getCurrentTabId(String category) {
Action action = getCurrentTabAction(category);
if (action != null) {
return action.getId();
}
return null;
}
public void setCurrentTabId(ActionManager actionManager,
ActionContext actionContext, String category, String tabId,
String... subTabIds) {
boolean set = false;
if (tabId != null && !WebActions.NULL_TAB_ID.equals(tabId)) {
if (actionManager.isEnabled(tabId, actionContext)) {
Action action = actionManager.getAction(tabId);
setCurrentTabAction(category, action);
if (subTabIds != null && subTabIds.length > 0) {
String newTabId = subTabIds[0];
String[] newSubTabsIds = new String[subTabIds.length - 1];
System.arraycopy(subTabIds, 1, newSubTabsIds, 0,
subTabIds.length - 1);
setCurrentTabId(actionManager, actionContext,
getSubTabCategory(tabId), newTabId, newSubTabsIds);
}
set = true;
} else {
if (actionManager.getAction(tabId) != null) {
log.warn(String.format(
"Cannot set current tab with id '%s': "
+ "action is not enabled.", tabId));
} else {
log.error(String.format(
"Cannot set current tab with id '%s': "
+ "action does not exist.", tabId));
}
}
}
if (!set && (tabId == null || WebActions.NULL_TAB_ID.equals(tabId))) {
resetCurrentTabs(category);
}
}
/**
* Returns current tab ids as a string, encoded as is:
* CATEGORY_1:ACTION_ID_1,CATEGORY_2:ACTION_ID_2:SUBTAB_ACTION_ID_2,...
*
* @since 5.4.2
*/
public String getCurrentTabIds() {
StringBuffer builder = new StringBuffer();
boolean first = true;
// resolve sub tabs
Map<String, List<Action>> actionsToEncode = new LinkedHashMap<String, List<Action>>();
Map<String, String> subTabToCategories = new HashMap<String, String>();
for (Map.Entry<String, Action> currentTabAction : currentTabActions.entrySet()) {
String category = currentTabAction.getKey();
Action action = currentTabAction.getValue();
subTabToCategories.put(getSubTabCategory(action.getId()), category);
if (subTabToCategories.containsKey(category)) {
// this is a sub action, parent already added
String cat = subTabToCategories.get(category);
List<Action> subActions = actionsToEncode.get(cat);
if (subActions == null) {
subActions = new ArrayList<Action>();
actionsToEncode.put(cat, subActions);
}
subActions.add(action);
} else {
List<Action> actionsList = new ArrayList<Action>();
actionsList.add(action);
actionsToEncode.put(category, actionsList);
}
}
for (Map.Entry<String, List<Action>> item : actionsToEncode.entrySet()) {
String encodedActions = encodeActions(item.getKey(),
item.getValue());
if (encodedActions != null) {
if (!first) {
builder.append(",");
}
first = false;
builder.append(encodedActions);
}
}
return builder.toString();
}
/**
* Sets current tab ids as a String, splitting on commas ',' and parsing
* each action information as is:
* CATEGORY:[ACTION_ID[:OPTIONAL_SUB_ACTION_ID
* [:OPTIONAL_SUB_ACTION_ID]...]]
* <p>
* If category is omitted or empty, the category
* {@link #DEFAULT_TABS_CATEGORY} will be used (if there is no subtab
* information).
* <p>
* If no action id is given, the corresponding category is reset (for
* instance using 'CATEGORY:').
* <p>
* If the action information is '*:', all categories will be reset.
* <p>
* The resulting string looks like:
* CATEGORY_1:ACTION_ID_1,CATEGORY_2:ACTION_ID_2_SUB_ACTION_ID_2,...
*
* @since 5.4.2
*/
public void setCurrentTabIds(ActionManager actionManager,
ActionContext actionContext, String tabIds) {
if (tabIds == null) {
return;
}
String[] encodedActions = tabIds.split(",");
if (encodedActions != null && encodedActions.length != 0) {
for (String encodedAction : encodedActions) {
encodedAction = encodedAction.trim();
if ((":").equals(encodedAction)) {
// reset default actions
resetCurrentTabs(WebActions.DEFAULT_TABS_CATEGORY);
} else {
String[] actionInfo = encodedAction.split(":");
// XXX: "*:" vs ":TRUC"
if (actionInfo != null && actionInfo.length == 1) {
if (encodedAction.startsWith(":")) {
// it's a default action
setCurrentTabId(actionManager, actionContext,
WebActions.DEFAULT_TABS_CATEGORY,
actionInfo[0]);
} else {
String category = actionInfo[0];
// it's a category, and it needs to be reset
if ("*".equals(category)) {
resetCurrentTabs();
} else {
resetCurrentTabs(category);
}
}
} else if (actionInfo != null && actionInfo.length > 1) {
String category = actionInfo[0];
String actionId = actionInfo[1];
String[] subTabsIds = new String[actionInfo.length - 2];
System.arraycopy(actionInfo, 2, subTabsIds, 0,
actionInfo.length - 2);
if (category == null || category.isEmpty()) {
category = WebActions.DEFAULT_TABS_CATEGORY;
}
setCurrentTabId(actionManager, actionContext, category,
actionId, subTabsIds);
} else {
log.error(String.format(
"Cannot set current tab from given encoded action: '%s'",
encodedAction));
}
}
}
}
}
/**
* Resets all current tabs information.
*
* @since 5.4.2
*/
public void resetCurrentTabs() {
Set<String> categories = currentTabActions.keySet();
currentTabActions.clear();
for (String category : categories) {
raiseEventOnCurrentTabSelected(category, null);
raiseEventOnCurrentTabChange(category, null);
}
}
/**
* Resets current tabs for given category, taking subtabs into account by
* resetting actions in categories computed from reset actions id with
* suffix {@link #SUBTAB_CATEGORY_SUFFIX}.
*/
public void resetCurrentTabs(String category) {
if (currentTabActions.containsKey(category)) {
Action action = currentTabActions.get(category);
currentTabActions.remove(category);
raiseEventOnCurrentTabSelected(category, null);
raiseEventOnCurrentTabChange(category, null);
if (action != null) {
resetCurrentTabs(getSubTabCategory(action.getId()));
}
}
}
protected String encodeActions(String category, List<Action> actions) {
if (actions == null || actions.isEmpty()) {
return null;
}
StringBuilder builder = new StringBuilder();
builder.append(WebActions.DEFAULT_TABS_CATEGORY.equals(category) ? ""
: category);
for (int i = 0; i < actions.size(); i++) {
builder.append(":" + actions.get(i).getId());
}
return builder.toString();
}
public static String getSubTabCategory(String parentActionId) {
if (parentActionId == null) {
return null;
}
return parentActionId + WebActions.SUBTAB_CATEGORY_SUFFIX;
}
/**
* Raises a seam event when current tab changes for a given category.
* <p>
* Actually raises 2 events: one with name
* WebActions#CURRENT_TAB_CHANGED_EVENT and another with name
* WebActions#CURRENT_TAB_CHANGED_EVENT + '_' + category to optimize
* observers declarations.
* <p>
* The event is always sent with 2 parameters: the category and tab id (the
* tab id can be null when resetting current tab for given category).
*
* @see WebActions#CURRENT_TAB_CHANGED_EVENT
* @since 5.4.2
*/
protected void raiseEventOnCurrentTabChange(String category, String tabId) {
if (Events.exists()) {
Events.instance().raiseEvent(WebActions.CURRENT_TAB_CHANGED_EVENT,
category, tabId);
Events.instance().raiseEvent(
WebActions.CURRENT_TAB_CHANGED_EVENT + "_" + category,
category, tabId);
}
}
/**
* Raises a seam event when current tab is selected for a given category.
* Fired also when current tab did not change.
* <p>
* Actually raises 2 events: one with name
* WebActions#CURRENT_TAB_SELECTED_EVENT and another with name
* WebActions#CURRENT_TAB_SELECTED_EVENT + '_' + category to optimize
* observers declarations.
* <p>
* The event is always sent with 2 parameters: the category and tab id (the
* tab id can be null when resetting current tab for given category).
*
* @see WebActions#CURRENT_TAB_SELECTED_EVENT
* @since 5.6
*/
protected void raiseEventOnCurrentTabSelected(String category, String tabId) {
if (Events.exists()) {
Events.instance().raiseEvent(WebActions.CURRENT_TAB_SELECTED_EVENT,
category, tabId);
Events.instance().raiseEvent(
WebActions.CURRENT_TAB_SELECTED_EVENT + "_" + category,
category, tabId);
}
}
}