001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Anahide Tchertchian 016 */ 017package org.nuxeo.ecm.platform.ui.web.api; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.jboss.seam.core.Events; 030import org.nuxeo.ecm.platform.actions.Action; 031import org.nuxeo.ecm.platform.actions.ActionContext; 032import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 033 034/** 035 * Handles selected action tabs and raised events on current tab change. 036 * 037 * @see WebActions#CURRENT_TAB_CHANGED_EVENT 038 * @since 5.4.2 039 */ 040public class TabActionsSelection implements Serializable { 041 042 private static final long serialVersionUID = 1L; 043 044 private static final Log log = LogFactory.getLog(TabActionsSelection.class); 045 046 /** 047 * Map of current tab actions, with category as key and corresponding action as value. 048 * <p> 049 * Use a linked has map to preserve order when using several selections as sub tabs management needs order to be 050 * preserved. 051 */ 052 protected Map<String, Action> currentTabActions = new LinkedHashMap<String, Action>(); 053 054 /** 055 * Returns the current action for given category. 056 */ 057 public Action getCurrentTabAction(String category) { 058 if (currentTabActions.containsKey(category)) { 059 return currentTabActions.get(category); 060 } 061 return null; 062 } 063 064 /** 065 * Sets the current action for given category, with additional sub tabs. 066 * <p> 067 * If given action is null, it resets the current action for this category. 068 */ 069 public void setCurrentTabAction(String category, Action tabAction) { 070 if (category == null) { 071 return; 072 } 073 if (tabAction != null) { 074 String[] actionCategories = tabAction.getCategories(); 075 if (actionCategories != null) { 076 boolean categoryFound = false; 077 for (String actionCategory : actionCategories) { 078 if (category.equals(actionCategory)) { 079 categoryFound = true; 080 Action oldAction = currentTabActions.get(category); 081 currentTabActions.put(category, tabAction); 082 raiseEventOnCurrentTabSelected(category, tabAction.getId()); 083 if (oldAction == null || !oldAction.getId().equals(tabAction.getId())) { 084 // only raise the event if action actually changed 085 raiseEventOnCurrentTabChange(category, tabAction.getId()); 086 } 087 if (oldAction != null) { 088 // additional cleanup of possible sub tabs 089 resetCurrentTabs(getSubTabCategory(oldAction.getId())); 090 } 091 break; 092 } 093 } 094 if (!categoryFound) { 095 log.error(String.format("Cannot set current action '%s' for category" 096 + " '%s' as this action does not " + "hold the given category.", tabAction.getId(), 097 category)); 098 } 099 } 100 } else { 101 resetCurrentTabs(category); 102 } 103 } 104 105 public String getCurrentTabId(String category) { 106 Action action = getCurrentTabAction(category); 107 if (action != null) { 108 return action.getId(); 109 } 110 return null; 111 } 112 113 public void setCurrentTabId(ActionManager actionManager, ActionContext actionContext, String category, 114 String tabId, String... subTabIds) { 115 boolean set = false; 116 if (tabId != null && !WebActions.NULL_TAB_ID.equals(tabId)) { 117 if (actionManager.isEnabled(tabId, actionContext)) { 118 Action action = actionManager.getAction(tabId); 119 setCurrentTabAction(category, action); 120 if (subTabIds != null && subTabIds.length > 0) { 121 String newTabId = subTabIds[0]; 122 String[] newSubTabsIds = new String[subTabIds.length - 1]; 123 System.arraycopy(subTabIds, 1, newSubTabsIds, 0, subTabIds.length - 1); 124 setCurrentTabId(actionManager, actionContext, getSubTabCategory(tabId), newTabId, newSubTabsIds); 125 } 126 set = true; 127 } else { 128 if (actionManager.getAction(tabId) != null) { 129 log.warn(String.format("Cannot set current tab with id '%s': " + "action is not enabled.", tabId)); 130 } else { 131 log.error(String.format("Cannot set current tab with id '%s': " + "action does not exist.", tabId)); 132 } 133 } 134 } 135 if (!set && (tabId == null || WebActions.NULL_TAB_ID.equals(tabId))) { 136 resetCurrentTabs(category); 137 } 138 } 139 140 /** 141 * Returns current tab ids as a string, encoded as is: 142 * CATEGORY_1:ACTION_ID_1,CATEGORY_2:ACTION_ID_2:SUBTAB_ACTION_ID_2,... 143 * 144 * @since 5.4.2 145 */ 146 public String getCurrentTabIds() { 147 StringBuffer builder = new StringBuffer(); 148 boolean first = true; 149 // resolve sub tabs 150 Map<String, List<Action>> actionsToEncode = new LinkedHashMap<String, List<Action>>(); 151 Map<String, String> subTabToCategories = new HashMap<String, String>(); 152 for (Map.Entry<String, Action> currentTabAction : currentTabActions.entrySet()) { 153 String category = currentTabAction.getKey(); 154 Action action = currentTabAction.getValue(); 155 subTabToCategories.put(getSubTabCategory(action.getId()), category); 156 if (subTabToCategories.containsKey(category)) { 157 // this is a sub action, parent already added 158 String cat = subTabToCategories.get(category); 159 List<Action> subActions = actionsToEncode.get(cat); 160 if (subActions == null) { 161 subActions = new ArrayList<Action>(); 162 actionsToEncode.put(cat, subActions); 163 } 164 subActions.add(action); 165 } else { 166 List<Action> actionsList = new ArrayList<Action>(); 167 actionsList.add(action); 168 actionsToEncode.put(category, actionsList); 169 } 170 } 171 for (Map.Entry<String, List<Action>> item : actionsToEncode.entrySet()) { 172 String encodedActions = encodeActions(item.getKey(), item.getValue()); 173 if (encodedActions != null) { 174 if (!first) { 175 builder.append(","); 176 } 177 first = false; 178 builder.append(encodedActions); 179 } 180 } 181 return builder.toString(); 182 } 183 184 /** 185 * Sets current tab ids as a String, splitting on commas ',' and parsing each action information as is: 186 * CATEGORY:[ACTION_ID[:OPTIONAL_SUB_ACTION_ID [:OPTIONAL_SUB_ACTION_ID]...]] 187 * <p> 188 * If category is omitted or empty, the category {@link #DEFAULT_TABS_CATEGORY} will be used (if there is no subtab 189 * information). 190 * <p> 191 * If no action id is given, the corresponding category is reset (for instance using 'CATEGORY:'). 192 * <p> 193 * If the action information is '*:', all categories will be reset. 194 * <p> 195 * The resulting string looks like: CATEGORY_1:ACTION_ID_1,CATEGORY_2:ACTION_ID_2_SUB_ACTION_ID_2,... 196 * 197 * @since 5.4.2 198 */ 199 public void setCurrentTabIds(ActionManager actionManager, ActionContext actionContext, String tabIds) { 200 if (tabIds == null) { 201 return; 202 } 203 String[] encodedActions = tabIds.split(","); 204 if (encodedActions != null && encodedActions.length != 0) { 205 for (String encodedAction : encodedActions) { 206 encodedAction = encodedAction.trim(); 207 if ((":").equals(encodedAction)) { 208 // reset default actions 209 resetCurrentTabs(WebActions.DEFAULT_TABS_CATEGORY); 210 } else { 211 String[] actionInfo = encodedAction.split(":"); 212 // XXX: "*:" vs ":TRUC" 213 if (actionInfo != null && actionInfo.length == 1) { 214 if (encodedAction.startsWith(":")) { 215 // it's a default action 216 setCurrentTabId(actionManager, actionContext, WebActions.DEFAULT_TABS_CATEGORY, 217 actionInfo[0]); 218 } else { 219 String category = actionInfo[0]; 220 // it's a category, and it needs to be reset 221 if ("*".equals(category)) { 222 resetCurrentTabs(); 223 } else { 224 resetCurrentTabs(category); 225 } 226 } 227 } else if (actionInfo != null && actionInfo.length > 1) { 228 String category = actionInfo[0]; 229 String actionId = actionInfo[1]; 230 String[] subTabsIds = new String[actionInfo.length - 2]; 231 System.arraycopy(actionInfo, 2, subTabsIds, 0, actionInfo.length - 2); 232 if (category == null || category.isEmpty()) { 233 category = WebActions.DEFAULT_TABS_CATEGORY; 234 } 235 setCurrentTabId(actionManager, actionContext, category, actionId, subTabsIds); 236 } else { 237 log.error(String.format("Cannot set current tab from given encoded action: '%s'", encodedAction)); 238 } 239 } 240 } 241 } 242 } 243 244 /** 245 * Resets all current tabs information. 246 * 247 * @since 5.4.2 248 */ 249 public void resetCurrentTabs() { 250 Set<String> categories = currentTabActions.keySet(); 251 currentTabActions.clear(); 252 for (String category : categories) { 253 raiseEventOnCurrentTabSelected(category, null); 254 raiseEventOnCurrentTabChange(category, null); 255 } 256 } 257 258 /** 259 * Resets current tabs for given category, taking subtabs into account by resetting actions in categories computed 260 * from reset actions id with suffix {@link #SUBTAB_CATEGORY_SUFFIX}. 261 */ 262 public void resetCurrentTabs(String category) { 263 if (currentTabActions.containsKey(category)) { 264 Action action = currentTabActions.get(category); 265 currentTabActions.remove(category); 266 raiseEventOnCurrentTabSelected(category, null); 267 raiseEventOnCurrentTabChange(category, null); 268 if (action != null) { 269 resetCurrentTabs(getSubTabCategory(action.getId())); 270 } 271 } 272 } 273 274 protected String encodeActions(String category, List<Action> actions) { 275 if (actions == null || actions.isEmpty()) { 276 return null; 277 } 278 StringBuilder builder = new StringBuilder(); 279 builder.append(WebActions.DEFAULT_TABS_CATEGORY.equals(category) ? "" : category); 280 for (int i = 0; i < actions.size(); i++) { 281 builder.append(":" + actions.get(i).getId()); 282 } 283 return builder.toString(); 284 } 285 286 public static String getSubTabCategory(String parentActionId) { 287 if (parentActionId == null) { 288 return null; 289 } 290 return parentActionId + WebActions.SUBTAB_CATEGORY_SUFFIX; 291 } 292 293 /** 294 * Raises a seam event when current tab changes for a given category. 295 * <p> 296 * Actually raises 2 events: one with name WebActions#CURRENT_TAB_CHANGED_EVENT and another with name 297 * WebActions#CURRENT_TAB_CHANGED_EVENT + '_' + category to optimize observers declarations. 298 * <p> 299 * The event is always sent with 2 parameters: the category and tab id (the tab id can be null when resetting 300 * current tab for given category). 301 * 302 * @see WebActions#CURRENT_TAB_CHANGED_EVENT 303 * @since 5.4.2 304 */ 305 protected void raiseEventOnCurrentTabChange(String category, String tabId) { 306 if (Events.exists()) { 307 Events.instance().raiseEvent(WebActions.CURRENT_TAB_CHANGED_EVENT, category, tabId); 308 Events.instance().raiseEvent(WebActions.CURRENT_TAB_CHANGED_EVENT + "_" + category, category, tabId); 309 } 310 } 311 312 /** 313 * Raises a seam event when current tab is selected for a given category. Fired also when current tab did not 314 * change. 315 * <p> 316 * Actually raises 2 events: one with name WebActions#CURRENT_TAB_SELECTED_EVENT and another with name 317 * WebActions#CURRENT_TAB_SELECTED_EVENT + '_' + category to optimize observers declarations. 318 * <p> 319 * The event is always sent with 2 parameters: the category and tab id (the tab id can be null when resetting 320 * current tab for given category). 321 * 322 * @see WebActions#CURRENT_TAB_SELECTED_EVENT 323 * @since 5.6 324 */ 325 protected void raiseEventOnCurrentTabSelected(String category, String tabId) { 326 if (Events.exists()) { 327 Events.instance().raiseEvent(WebActions.CURRENT_TAB_SELECTED_EVENT, category, tabId); 328 Events.instance().raiseEvent(WebActions.CURRENT_TAB_SELECTED_EVENT + "_" + category, category, tabId); 329 } 330 } 331 332}