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> getDocumentActions(DocumentModel document, String category, boolean removeFiltered, 101 boolean postFilter) { 102 ActionContext context = postFilter ? null : createActionContext(document); 103 return getActions(context, category, removeFiltered, postFilter); 104 } 105 106 @Override 107 public Action getDocumentAction(DocumentModel document, String actionId, boolean removeFiltered, boolean postFilter) { 108 ActionContext context = postFilter ? null : createActionContext(document); 109 return getAction(context, actionId, removeFiltered, postFilter); 110 } 111 112 @Override 113 public List<Action> getActions(ActionContext context, String category, boolean removeFiltered, boolean postFilter) { 114 List<Action> list = new ArrayList<Action>(); 115 List<String> categories = new ArrayList<String>(); 116 if (category != null) { 117 String[] split = category.split(",|\\s"); 118 if (split != null) { 119 for (String item : split) { 120 if (!StringUtils.isBlank(item)) { 121 categories.add(item.trim()); 122 } 123 } 124 } 125 } 126 for (String cat : categories) { 127 List<Action> actions; 128 if (postFilter) { 129 actions = actionManager.getAllActions(cat); 130 } else { 131 actions = actionManager.getActions(cat, context, removeFiltered); 132 } 133 if (actions != null) { 134 list.addAll(actions); 135 } 136 } 137 return list; 138 } 139 140 @Override 141 public Action getAction(ActionContext context, String actionId, boolean removeFiltered, boolean postFilter) { 142 if (postFilter) { 143 return actionManager.getAction(actionId); 144 } 145 return actionManager.getAction(actionId, context, removeFiltered); 146 147 } 148 149 @Override 150 public boolean isAvailableForDocument(DocumentModel document, Action action) { 151 return isAvailable(createActionContext(document), action); 152 } 153 154 @Override 155 public boolean isAvailable(ActionContext context, Action action) { 156 if (action == null) { 157 return false; 158 } 159 if (action.isFiltered()) { 160 return action.getAvailable(); 161 } 162 return actionManager.checkFilters(action, context); 163 } 164 165 @Override 166 public List<Action> getActionsList(String category, ActionContext context, boolean removeFiltered) { 167 return getActions(context, category, removeFiltered, false); 168 } 169 170 @Override 171 public List<Action> getActionsList(String category, Boolean removeFiltered) { 172 return getActions(createActionContext(), category, removeFiltered, false); 173 } 174 175 @Override 176 public List<Action> getActionsList(String category, ActionContext context) { 177 return getActions(context, category, true, false); 178 } 179 180 @Override 181 public List<Action> getActionsListForDocument(String category, DocumentModel document, boolean removeFiltered) { 182 return getActions(createActionContext(document), category, removeFiltered, false); 183 } 184 185 @Override 186 public List<Action> getActionsList(String category) { 187 return getActions(createActionContext(), category, true, false); 188 } 189 190 @Override 191 public List<Action> getUnfiltredActionsList(String category, ActionContext context) { 192 return getActions(context, category, false, false); 193 } 194 195 @Override 196 public List<Action> getUnfiltredActionsList(String category) { 197 return getActions(createActionContext(), category, false, false); 198 } 199 200 @Override 201 public List<Action> getAllActions(String category) { 202 return actionManager.getAllActions(category); 203 } 204 205 protected ActionContext createActionContext() { 206 return actionContextProvider.createActionContext(); 207 } 208 209 protected ActionContext createActionContext(DocumentModel document) { 210 return actionContextProvider.createActionContext(document); 211 } 212 213 @Override 214 public Action getAction(String actionId, boolean removeFiltered) { 215 return getAction(createActionContext(), actionId, removeFiltered, false); 216 } 217 218 @Override 219 public Action getActionForDocument(String actionId, DocumentModel document, boolean removeFiltered) { 220 return getAction(createActionContext(document), actionId, removeFiltered, false); 221 } 222 223 @Override 224 public Action getAction(String actionId, ActionContext context, boolean removeFiltered) { 225 return getAction(context, actionId, removeFiltered, false); 226 } 227 228 @Override 229 public boolean checkFilter(String filterId) { 230 return actionManager.checkFilter(filterId, createActionContext()); 231 } 232 233 // tabs management 234 235 protected Action getDefaultTab(String category) { 236 if (DEFAULT_TABS_CATEGORY.equals(category)) { 237 if (getTabsList() == null) { 238 return null; 239 } 240 try { 241 return tabsActionsList.get(0); 242 } catch (IndexOutOfBoundsException e) { 243 return null; 244 } 245 } else { 246 // check if it's a subtab 247 if (subTabsCategory != null && subTabsCategory.equals(category)) { 248 if (getSubTabsList() == null) { 249 return null; 250 } 251 try { 252 return subTabsActionsList.get(0); 253 } catch (IndexOutOfBoundsException e) { 254 return null; 255 } 256 } 257 // retrieve actions in given category and take the first one found 258 List<Action> actions = getActionsList(category, createActionContext()); 259 if (actions != null && actions.size() > 0) { 260 // make sure selection event is sent 261 Action action = actions.get(0); 262 setCurrentTabAction(category, action); 263 return action; 264 } 265 return null; 266 } 267 268 } 269 270 @Override 271 public Action getCurrentTabAction(String category) { 272 Action action = currentTabActions.getCurrentTabAction(category); 273 if (action == null) { 274 // return default action 275 action = getDefaultTab(category); 276 } 277 return action; 278 } 279 280 @Override 281 public void setCurrentTabAction(String category, Action tabAction) { 282 currentTabActions.setCurrentTabAction(category, tabAction); 283 // additional cleanup of this cache 284 if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { 285 resetSubTabs(); 286 } 287 } 288 289 @Override 290 public Action getCurrentSubTabAction(String parentActionId) { 291 return getCurrentTabAction(TabActionsSelection.getSubTabCategory(parentActionId)); 292 } 293 294 @Override 295 public String getCurrentTabId(String category) { 296 Action action = getCurrentTabAction(category); 297 if (action != null) { 298 return action.getId(); 299 } 300 return null; 301 } 302 303 @Override 304 public boolean hasCurrentTabId(String category) { 305 if (currentTabActions.getCurrentTabAction(category) == null) { 306 return false; 307 } 308 return true; 309 } 310 311 @Override 312 public void setCurrentTabId(String category, String tabId, String... subTabIds) { 313 currentTabActions.setCurrentTabId(actionManager, createActionContext(), category, tabId, subTabIds); 314 // additional cleanup of this cache 315 if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { 316 resetSubTabs(); 317 } 318 } 319 320 @Override 321 public String getCurrentTabIds() { 322 return currentTabActions.getCurrentTabIds(); 323 } 324 325 @Override 326 public void setCurrentTabIds(String tabIds) { 327 currentTabActions.setCurrentTabIds(actionManager, createActionContext(), tabIds); 328 // reset subtabs just in case 329 resetSubTabs(); 330 } 331 332 @Override 333 public void resetCurrentTabs() { 334 currentTabActions.resetCurrentTabs(); 335 } 336 337 @Override 338 public void resetCurrentTabs(String category) { 339 currentTabActions.resetCurrentTabs(category); 340 } 341 342 // tabs management specific to the DEFAULT_TABS_CATEGORY 343 344 @Override 345 public void resetCurrentTab() { 346 resetCurrentTabs(DEFAULT_TABS_CATEGORY); 347 } 348 349 protected void resetSubTabs() { 350 subTabsCategory = null; 351 subTabsActionsList = null; 352 // make sure event context is cleared so that factory is called again 353 Contexts.getEventContext().remove("subTabsActionsList"); 354 Contexts.getEventContext().remove("currentSubTabAction"); 355 } 356 357 @Override 358 @Observer(value = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, 359 EventNames.LOCATION_SELECTION_CHANGED }, create = false) 360 @BypassInterceptors 361 public void resetTabList() { 362 tabsActionsList = null; 363 resetSubTabs(); 364 resetCurrentTab(); 365 // make sure event context is cleared so that factory is called again 366 Contexts.getEventContext().remove("tabsActionsList"); 367 Contexts.getEventContext().remove("currentTabAction"); 368 } 369 370 @Override 371 @Factory(value = "tabsActionsList", scope = EVENT) 372 public List<Action> getTabsList() { 373 if (tabsActionsList == null) { 374 tabsActionsList = getActionsList(DEFAULT_TABS_CATEGORY); 375 } 376 return tabsActionsList; 377 } 378 379 @Override 380 @Factory(value = "subTabsActionsList", scope = EVENT) 381 public List<Action> getSubTabsList() { 382 if (subTabsActionsList == null) { 383 String currentTabId = getCurrentTabId(); 384 if (currentTabId != null) { 385 subTabsCategory = TabActionsSelection.getSubTabCategory(currentTabId); 386 subTabsActionsList = getActionsList(subTabsCategory); 387 } 388 } 389 return subTabsActionsList; 390 } 391 392 @Override 393 @Factory(value = "currentTabAction", scope = EVENT) 394 public Action getCurrentTabAction() { 395 return getCurrentTabAction(DEFAULT_TABS_CATEGORY); 396 } 397 398 @Override 399 public void setCurrentTabAction(Action currentTabAction) { 400 setCurrentTabAction(DEFAULT_TABS_CATEGORY, currentTabAction); 401 } 402 403 @Override 404 @Factory(value = "currentSubTabAction", scope = EVENT) 405 public Action getCurrentSubTabAction() { 406 Action action = getCurrentTabAction(); 407 if (action != null) { 408 return getCurrentTabAction(TabActionsSelection.getSubTabCategory(action.getId())); 409 } 410 return null; 411 } 412 413 @Override 414 public void setCurrentSubTabAction(Action tabAction) { 415 if (tabAction != null) { 416 String[] categories = tabAction.getCategories(); 417 if (categories == null || categories.length == 0) { 418 log.error("Cannot set subtab with id '" + tabAction.getId() 419 + "' as this action does not hold any category"); 420 return; 421 } 422 if (categories.length != 1) { 423 log.error("Setting subtab with id '" + tabAction.getId() + "' with category '" + categories[0] 424 + "': use webActions#setCurrentTabAction(action, category) to specify another category"); 425 } 426 setCurrentTabAction(categories[0], tabAction); 427 } 428 } 429 430 @Override 431 public String getCurrentTabId() { 432 Action currentTab = getCurrentTabAction(); 433 if (currentTab != null) { 434 return currentTab.getId(); 435 } 436 return null; 437 } 438 439 @Override 440 public void setCurrentTabId(String tabId) { 441 if (tabId != null) { 442 // do not reset tab when not set as this method 443 // is used for compatibility in default url pattern 444 setCurrentTabId(DEFAULT_TABS_CATEGORY, tabId); 445 } 446 } 447 448 @Override 449 public String getCurrentSubTabId() { 450 Action currentSubTab = getCurrentSubTabAction(); 451 if (currentSubTab != null) { 452 return currentSubTab.getId(); 453 } 454 return null; 455 } 456 457 @Override 458 public void setCurrentSubTabId(String tabId) { 459 if (tabId != null) { 460 // do not reset tab when not set as this method 461 // is used for compatibility in default url pattern 462 Action action = getCurrentTabAction(); 463 if (action != null) { 464 setCurrentTabId(TabActionsSelection.getSubTabCategory(action.getId()), tabId); 465 } 466 } 467 } 468 469 // navigation API 470 471 @Override 472 public String setCurrentTabAndNavigate(String currentTabActionId) { 473 return setCurrentTabAndNavigate(navigationContext.getCurrentDocument(), currentTabActionId); 474 } 475 476 @Override 477 public String setCurrentTabAndNavigate(DocumentModel document, String currentTabActionId) { 478 // navigate first because it will reset the tabs list 479 String viewId = null; 480 try { 481 viewId = navigationContext.navigateToDocument(document); 482 } catch (NuxeoException e) { 483 log.error("Failed to navigate to " + document, e); 484 } 485 // force creation of new actions if needed 486 getTabsList(); 487 // set current tab 488 setCurrentTabId(currentTabActionId); 489 return viewId; 490 } 491 492 // deprecated API 493 494 @Override 495 @Deprecated 496 public List<Action> getSubViewActionsList() { 497 return getActionsList("SUBVIEW_UPPER_LIST"); 498 } 499 500 @Override 501 @Deprecated 502 public void selectTabAction() { 503 // if (tabAction != null) { 504 // setCurrentTabAction(tabAction); 505 // } 506 } 507 508 @Override 509 @Deprecated 510 public String getCurrentLifeCycleState() { 511 // only user of documentManager in this bean, look it up by hand 512 CoreSession documentManager = (CoreSession) Component.getInstance("documentManager"); 513 return documentManager.getCurrentLifeCycleState(navigationContext.getCurrentDocument().getRef()); 514 } 515 516 @Override 517 @Deprecated 518 public void setTabsList(List<Action> tabsList) { 519 tabsActionsList = tabsList; 520 } 521 522 @Override 523 @Deprecated 524 public void setSubTabsList(List<Action> tabsList) { 525 subTabsActionsList = tabsList; 526 subTabsCategory = null; 527 if (tabsList != null) { 528 // BBB code 529 for (Action action : tabsList) { 530 if (action != null) { 531 String[] categories = action.getCategories(); 532 if (categories != null && categories.length > 0) { 533 subTabsCategory = categories[0]; 534 break; 535 } 536 } 537 } 538 } 539 } 540 541 @Override 542 @Deprecated 543 public void setCurrentTabAction(String currentTabActionId) { 544 setCurrentTabId(currentTabActionId); 545 } 546 547 @Override 548 @Factory(value = "useAjaxTabs", scope = ScopeType.SESSION) 549 public boolean useAjaxTabs() { 550 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 551 if (configurationService.isBooleanPropertyTrue(AJAX_TAB_PROPERTY)) { 552 return canUseAjaxTabs(); 553 } 554 return false; 555 } 556 557 @Override 558 @Factory(value = "canUseAjaxTabs", scope = ScopeType.SESSION) 559 public boolean canUseAjaxTabs() { 560 FacesContext context = FacesContext.getCurrentInstance(); 561 ExternalContext econtext = context.getExternalContext(); 562 HttpServletRequest request = (HttpServletRequest) econtext.getRequest(); 563 String ua = request.getHeader("User-Agent"); 564 return UserAgentMatcher.isHistoryPushStateSupported(ua); 565 } 566 567 /** 568 * Returns true if configuration property to remove optimizations around actions (for compatibility) has been 569 * enabled. 570 * 571 * @since 8.2 572 */ 573 @Factory(value = "removeActionOptims", scope = ScopeType.SESSION) 574 public boolean removeActionOptims() { 575 ConfigurationService cs = Framework.getService(ConfigurationService.class); 576 return cs.isBooleanPropertyTrue("nuxeo.jsf.actions.removeActionOptims"); 577 } 578 579 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 580 @BypassInterceptors 581 public void onHotReloadFlush() { 582 // reset above caches 583 Context seamContext = Contexts.getSessionContext(); 584 seamContext.remove("useAjaxTabs"); 585 seamContext.remove("canUseAjaxTabs"); 586 } 587 588}