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