/* * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * 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("Cannot set current action '" + tabAction.getId() + "' for category '" + category + "' as this action does not hold the given 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("Cannot set current tab with id '" + tabId + "': action is not enabled."); } else { log.error("Cannot set current tab with id '" + tabId + "': action does not exist."); } } } 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("Cannot set current tab from given encoded action: '" + 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); } } }