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