001/* 002 * (C) Copyright 2007-2008 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 * Eugen Ionica 018 * Anahide Tchertchian 019 * Florent Guillaume 020 */ 021 022package org.nuxeo.ecm.webapp.action; 023 024import static org.jboss.seam.ScopeType.CONVERSATION; 025import static org.jboss.seam.ScopeType.EVENT; 026 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.List; 030 031import javax.faces.context.ExternalContext; 032import javax.faces.context.FacesContext; 033import javax.servlet.http.HttpServletRequest; 034 035import org.apache.commons.lang.StringUtils; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.jboss.seam.Component; 039import org.jboss.seam.ScopeType; 040import org.jboss.seam.annotations.Factory; 041import org.jboss.seam.annotations.In; 042import org.jboss.seam.annotations.Install; 043import org.jboss.seam.annotations.Name; 044import org.jboss.seam.annotations.Observer; 045import org.jboss.seam.annotations.Scope; 046import org.jboss.seam.annotations.intercept.BypassInterceptors; 047import org.jboss.seam.contexts.Context; 048import org.jboss.seam.contexts.Contexts; 049import org.nuxeo.common.utils.UserAgentMatcher; 050import org.nuxeo.ecm.core.api.CoreSession; 051import org.nuxeo.ecm.core.api.DocumentModel; 052import org.nuxeo.ecm.core.api.NuxeoException; 053import org.nuxeo.ecm.platform.actions.Action; 054import org.nuxeo.ecm.platform.actions.ActionContext; 055import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 056import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 057import org.nuxeo.ecm.platform.ui.web.api.TabActionsSelection; 058import org.nuxeo.ecm.platform.ui.web.api.WebActions; 059import org.nuxeo.ecm.webapp.helpers.EventNames; 060import org.nuxeo.runtime.api.Framework; 061import org.nuxeo.runtime.services.config.ConfigurationService; 062 063/** 064 * Component that handles actions retrieval as well as current tab(s) selection. 065 * 066 * @author Eugen Ionica 067 * @author Anahide Tchertchian 068 * @author Florent Guillaume 069 * @author <a href="mailto:cbaican@nuxeo.com">Catalin Baican</a> 070 */ 071@Name("webActions") 072@Scope(CONVERSATION) 073@Install(precedence = Install.FRAMEWORK) 074public class WebActionsBean implements WebActions, Serializable { 075 076 private static final long serialVersionUID = 1959221536502251848L; 077 078 private static final Log log = LogFactory.getLog(WebActionsBean.class); 079 080 @In(create = true, required = false) 081 protected transient ActionManager actionManager; 082 083 @In(create = true, required = false) 084 protected transient ActionContextProvider actionContextProvider; 085 086 @In(create = true, required = false) 087 protected transient NavigationContext navigationContext; 088 089 protected List<Action> tabsActionsList; 090 091 protected String subTabsCategory; 092 093 protected List<Action> subTabsActionsList; 094 095 protected TabActionsSelection currentTabActions = new TabActionsSelection(); 096 097 // actions management 098 099 @Override 100 public List<Action> getActionsList(String category, ActionContext context, boolean hideUnavailableAction) { 101 List<Action> list = new ArrayList<Action>(); 102 List<String> categories = new ArrayList<String>(); 103 if (category != null) { 104 String[] split = category.split(",|\\s"); 105 if (split != null) { 106 for (String item : split) { 107 if (!StringUtils.isBlank(item)) { 108 categories.add(item.trim()); 109 } 110 } 111 } 112 } 113 for (String cat : categories) { 114 List<Action> actions = actionManager.getActions(cat, context, hideUnavailableAction); 115 if (actions != null) { 116 list.addAll(actions); 117 } 118 } 119 return list; 120 } 121 122 @Override 123 public List<Action> getActionsList(String category, Boolean hideUnavailableAction) { 124 return getActionsList(category, createActionContext(), Boolean.TRUE.equals(hideUnavailableAction)); 125 } 126 127 public List<Action> getActionsList(String category, ActionContext context) { 128 return getActionsList(category, context, true); 129 } 130 131 @Override 132 public List<Action> getActionsListForDocument(String category, DocumentModel document, 133 boolean hideUnavailableAction) { 134 return getActionsList(category, createActionContext(document), hideUnavailableAction); 135 } 136 137 public List<Action> getActionsList(String category) { 138 return getActionsList(category, createActionContext()); 139 } 140 141 public List<Action> getUnfiltredActionsList(String category, ActionContext context) { 142 return getActionsList(category, context, false); 143 } 144 145 public List<Action> getUnfiltredActionsList(String category) { 146 return getUnfiltredActionsList(category, createActionContext()); 147 } 148 149 public List<Action> getAllActions(String category) { 150 return actionManager.getAllActions(category); 151 } 152 153 protected ActionContext createActionContext() { 154 return actionContextProvider.createActionContext(); 155 } 156 157 protected ActionContext createActionContext(DocumentModel document) { 158 return actionContextProvider.createActionContext(document); 159 } 160 161 @Override 162 public Action getAction(String actionId, boolean hideUnavailableAction) { 163 return actionManager.getAction(actionId, createActionContext(), hideUnavailableAction); 164 } 165 166 @Override 167 public Action getActionForDocument(String actionId, DocumentModel document, boolean hideUnavailableAction) { 168 return getAction(actionId, createActionContext(document), hideUnavailableAction); 169 } 170 171 @Override 172 public Action getAction(String actionId, ActionContext context, boolean hideUnavailableAction) { 173 return actionManager.getAction(actionId, context, hideUnavailableAction); 174 } 175 176 @Override 177 public boolean checkFilter(String filterId) { 178 return actionManager.checkFilter(filterId, createActionContext()); 179 } 180 181 // tabs management 182 183 protected Action getDefaultTab(String category) { 184 if (DEFAULT_TABS_CATEGORY.equals(category)) { 185 if (getTabsList() == null) { 186 return null; 187 } 188 try { 189 return tabsActionsList.get(0); 190 } catch (IndexOutOfBoundsException e) { 191 return null; 192 } 193 } else { 194 // check if it's a subtab 195 if (subTabsCategory != null && subTabsCategory.equals(category)) { 196 if (getSubTabsList() == null) { 197 return null; 198 } 199 try { 200 return subTabsActionsList.get(0); 201 } catch (IndexOutOfBoundsException e) { 202 return null; 203 } 204 } 205 // retrieve actions in given category and take the first one found 206 List<Action> actions = getActionsList(category, createActionContext()); 207 if (actions != null && actions.size() > 0) { 208 // make sure selection event is sent 209 Action action = actions.get(0); 210 setCurrentTabAction(category, action); 211 return action; 212 } 213 return null; 214 } 215 216 } 217 218 @Override 219 public Action getCurrentTabAction(String category) { 220 Action action = currentTabActions.getCurrentTabAction(category); 221 if (action == null) { 222 // return default action 223 action = getDefaultTab(category); 224 } 225 return action; 226 } 227 228 @Override 229 public void setCurrentTabAction(String category, Action tabAction) { 230 currentTabActions.setCurrentTabAction(category, tabAction); 231 // additional cleanup of this cache 232 if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { 233 resetSubTabs(); 234 } 235 } 236 237 @Override 238 public Action getCurrentSubTabAction(String parentActionId) { 239 return getCurrentTabAction(TabActionsSelection.getSubTabCategory(parentActionId)); 240 } 241 242 @Override 243 public String getCurrentTabId(String category) { 244 Action action = getCurrentTabAction(category); 245 if (action != null) { 246 return action.getId(); 247 } 248 return null; 249 } 250 251 public boolean hasCurrentTabId(String category) { 252 if (currentTabActions.getCurrentTabAction(category) == null) { 253 return false; 254 } 255 return true; 256 } 257 258 @Override 259 public void setCurrentTabId(String category, String tabId, String... subTabIds) { 260 currentTabActions.setCurrentTabId(actionManager, createActionContext(), category, tabId, subTabIds); 261 // additional cleanup of this cache 262 if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { 263 resetSubTabs(); 264 } 265 } 266 267 @Override 268 public String getCurrentTabIds() { 269 return currentTabActions.getCurrentTabIds(); 270 } 271 272 @Override 273 public void setCurrentTabIds(String tabIds) { 274 currentTabActions.setCurrentTabIds(actionManager, createActionContext(), tabIds); 275 // reset subtabs just in case 276 resetSubTabs(); 277 } 278 279 @Override 280 public void resetCurrentTabs() { 281 currentTabActions.resetCurrentTabs(); 282 } 283 284 @Override 285 public void resetCurrentTabs(String category) { 286 currentTabActions.resetCurrentTabs(category); 287 } 288 289 // tabs management specific to the DEFAULT_TABS_CATEGORY 290 291 public void resetCurrentTab() { 292 resetCurrentTabs(DEFAULT_TABS_CATEGORY); 293 } 294 295 protected void resetSubTabs() { 296 subTabsCategory = null; 297 subTabsActionsList = null; 298 // make sure event context is cleared so that factory is called again 299 Contexts.getEventContext().remove("subTabsActionsList"); 300 Contexts.getEventContext().remove("currentSubTabAction"); 301 } 302 303 @Observer(value = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, 304 EventNames.LOCATION_SELECTION_CHANGED }, create = false) 305 @BypassInterceptors 306 public void resetTabList() { 307 tabsActionsList = null; 308 resetSubTabs(); 309 resetCurrentTab(); 310 // make sure event context is cleared so that factory is called again 311 Contexts.getEventContext().remove("tabsActionsList"); 312 Contexts.getEventContext().remove("currentTabAction"); 313 } 314 315 @Factory(value = "tabsActionsList", scope = EVENT) 316 public List<Action> getTabsList() { 317 if (tabsActionsList == null) { 318 tabsActionsList = getActionsList(DEFAULT_TABS_CATEGORY); 319 } 320 return tabsActionsList; 321 } 322 323 @Factory(value = "subTabsActionsList", scope = EVENT) 324 public List<Action> getSubTabsList() { 325 if (subTabsActionsList == null) { 326 String currentTabId = getCurrentTabId(); 327 if (currentTabId != null) { 328 subTabsCategory = TabActionsSelection.getSubTabCategory(currentTabId); 329 subTabsActionsList = getActionsList(subTabsCategory); 330 } 331 } 332 return subTabsActionsList; 333 } 334 335 @Factory(value = "currentTabAction", scope = EVENT) 336 public Action getCurrentTabAction() { 337 return getCurrentTabAction(DEFAULT_TABS_CATEGORY); 338 } 339 340 public void setCurrentTabAction(Action currentTabAction) { 341 setCurrentTabAction(DEFAULT_TABS_CATEGORY, currentTabAction); 342 } 343 344 @Factory(value = "currentSubTabAction", scope = EVENT) 345 public Action getCurrentSubTabAction() { 346 Action action = getCurrentTabAction(); 347 if (action != null) { 348 return getCurrentTabAction(TabActionsSelection.getSubTabCategory(action.getId())); 349 } 350 return null; 351 } 352 353 public void setCurrentSubTabAction(Action tabAction) { 354 if (tabAction != null) { 355 String[] categories = tabAction.getCategories(); 356 if (categories == null || categories.length == 0) { 357 log.error("Cannot set subtab with id '" + tabAction.getId() 358 + "' as this action does not hold any category"); 359 return; 360 } 361 if (categories.length != 1) { 362 log.error("Setting subtab with id '" + tabAction.getId() + "' with category '" + categories[0] 363 + "': use webActions#setCurrentTabAction(action, category) to specify another category"); 364 } 365 setCurrentTabAction(categories[0], tabAction); 366 } 367 } 368 369 public String getCurrentTabId() { 370 Action currentTab = getCurrentTabAction(); 371 if (currentTab != null) { 372 return currentTab.getId(); 373 } 374 return null; 375 } 376 377 public void setCurrentTabId(String tabId) { 378 if (tabId != null) { 379 // do not reset tab when not set as this method 380 // is used for compatibility in default url pattern 381 setCurrentTabId(DEFAULT_TABS_CATEGORY, tabId); 382 } 383 } 384 385 public String getCurrentSubTabId() { 386 Action currentSubTab = getCurrentSubTabAction(); 387 if (currentSubTab != null) { 388 return currentSubTab.getId(); 389 } 390 return null; 391 } 392 393 public void setCurrentSubTabId(String tabId) { 394 if (tabId != null) { 395 // do not reset tab when not set as this method 396 // is used for compatibility in default url pattern 397 Action action = getCurrentTabAction(); 398 if (action != null) { 399 setCurrentTabId(TabActionsSelection.getSubTabCategory(action.getId()), tabId); 400 } 401 } 402 } 403 404 // navigation API 405 406 public String setCurrentTabAndNavigate(String currentTabActionId) { 407 return setCurrentTabAndNavigate(navigationContext.getCurrentDocument(), currentTabActionId); 408 } 409 410 public String setCurrentTabAndNavigate(DocumentModel document, String currentTabActionId) { 411 // navigate first because it will reset the tabs list 412 String viewId = null; 413 try { 414 viewId = navigationContext.navigateToDocument(document); 415 } catch (NuxeoException e) { 416 log.error("Failed to navigate to " + document, e); 417 } 418 // force creation of new actions if needed 419 getTabsList(); 420 // set current tab 421 setCurrentTabId(currentTabActionId); 422 return viewId; 423 } 424 425 // deprecated API 426 427 @Deprecated 428 public List<Action> getSubViewActionsList() { 429 return getActionsList("SUBVIEW_UPPER_LIST"); 430 } 431 432 @Deprecated 433 public void selectTabAction() { 434 // if (tabAction != null) { 435 // setCurrentTabAction(tabAction); 436 // } 437 } 438 439 @Deprecated 440 public String getCurrentLifeCycleState() { 441 // only user of documentManager in this bean, look it up by hand 442 CoreSession documentManager = (CoreSession) Component.getInstance("documentManager"); 443 return documentManager.getCurrentLifeCycleState(navigationContext.getCurrentDocument().getRef()); 444 } 445 446 @Deprecated 447 public void setTabsList(List<Action> tabsList) { 448 tabsActionsList = tabsList; 449 } 450 451 @Deprecated 452 public void setSubTabsList(List<Action> tabsList) { 453 subTabsActionsList = tabsList; 454 subTabsCategory = null; 455 if (tabsList != null) { 456 // BBB code 457 for (Action action : tabsList) { 458 if (action != null) { 459 String[] categories = action.getCategories(); 460 if (categories != null && categories.length > 0) { 461 subTabsCategory = categories[0]; 462 break; 463 } 464 } 465 } 466 } 467 } 468 469 @Deprecated 470 public void setCurrentTabAction(String currentTabActionId) { 471 setCurrentTabId(currentTabActionId); 472 } 473 474 @Factory(value = "useAjaxTabs", scope = ScopeType.SESSION) 475 public boolean useAjaxTabs() { 476 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 477 if (configurationService.isBooleanPropertyTrue(AJAX_TAB_PROPERTY)) { 478 return canUseAjaxTabs(); 479 } 480 return false; 481 } 482 483 @Factory(value = "canUseAjaxTabs", scope = ScopeType.SESSION) 484 public boolean canUseAjaxTabs() { 485 FacesContext context = FacesContext.getCurrentInstance(); 486 ExternalContext econtext = context.getExternalContext(); 487 HttpServletRequest request = (HttpServletRequest) econtext.getRequest(); 488 String ua = request.getHeader("User-Agent"); 489 return UserAgentMatcher.isHistoryPushStateSupported(ua); 490 } 491 492 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 493 @BypassInterceptors 494 public void onHotReloadFlush() { 495 // reset above caches 496 Context seamContext = Contexts.getSessionContext(); 497 seamContext.remove("useAjaxTabs"); 498 seamContext.remove("canUseAjaxTabs"); 499 } 500 501}