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