/* * (C) Copyright 2007-2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Eugen Ionica * Anahide Tchertchian * Florent Guillaume */ package org.nuxeo.ecm.webapp.action; import static org.jboss.seam.ScopeType.CONVERSATION; import static org.jboss.seam.ScopeType.EVENT; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Context; import org.jboss.seam.contexts.Contexts; import org.nuxeo.common.utils.UserAgentMatcher; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.platform.actions.Action; import org.nuxeo.ecm.platform.actions.ActionContext; import org.nuxeo.ecm.platform.actions.ejb.ActionManager; import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; import org.nuxeo.ecm.platform.ui.web.api.TabActionsSelection; import org.nuxeo.ecm.platform.ui.web.api.WebActions; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.services.config.ConfigurationService; /** * Component that handles actions retrieval as well as current tab(s) selection. * * @author Eugen Ionica * @author Anahide Tchertchian * @author Florent Guillaume * @author <a href="mailto:cbaican@nuxeo.com">Catalin Baican</a> */ @Name("webActions") @Scope(CONVERSATION) @Install(precedence = Install.FRAMEWORK) public class WebActionsBean implements WebActions, Serializable { private static final long serialVersionUID = 1959221536502251848L; private static final Log log = LogFactory.getLog(WebActionsBean.class); @In(create = true, required = false) protected transient ActionManager actionManager; @In(create = true, required = false) protected transient ActionContextProvider actionContextProvider; @In(create = true, required = false) protected transient NavigationContext navigationContext; protected List<Action> tabsActionsList; protected String subTabsCategory; protected List<Action> subTabsActionsList; protected TabActionsSelection currentTabActions = new TabActionsSelection(); // actions management @Override public List<Action> getDocumentActions(DocumentModel document, String category, boolean removeFiltered, boolean postFilter) { ActionContext context = postFilter ? null : createActionContext(document); return getActions(context, category, removeFiltered, postFilter); } @Override public Action getDocumentAction(DocumentModel document, String actionId, boolean removeFiltered, boolean postFilter) { ActionContext context = postFilter ? null : createActionContext(document); return getAction(context, actionId, removeFiltered, postFilter); } @Override public List<Action> getActions(ActionContext context, String category, boolean removeFiltered, boolean postFilter) { List<Action> list = new ArrayList<Action>(); List<String> categories = new ArrayList<String>(); if (category != null) { String[] split = category.split(",|\\s"); if (split != null) { for (String item : split) { if (!StringUtils.isBlank(item)) { categories.add(item.trim()); } } } } for (String cat : categories) { List<Action> actions; if (postFilter) { actions = actionManager.getAllActions(cat); } else { actions = actionManager.getActions(cat, context, removeFiltered); } if (actions != null) { list.addAll(actions); } } return list; } @Override public Action getAction(ActionContext context, String actionId, boolean removeFiltered, boolean postFilter) { if (postFilter) { return actionManager.getAction(actionId); } return actionManager.getAction(actionId, context, removeFiltered); } @Override public boolean isAvailableForDocument(DocumentModel document, Action action) { return isAvailable(createActionContext(document), action); } @Override public boolean isAvailable(ActionContext context, Action action) { if (action == null) { return false; } if (action.isFiltered()) { return action.getAvailable(); } return actionManager.checkFilters(action, context); } @Override public List<Action> getActionsList(String category, ActionContext context, boolean removeFiltered) { return getActions(context, category, removeFiltered, false); } @Override public List<Action> getActionsList(String category, Boolean removeFiltered) { return getActions(createActionContext(), category, removeFiltered, false); } @Override public List<Action> getActionsList(String category, ActionContext context) { return getActions(context, category, true, false); } @Override public List<Action> getActionsListForDocument(String category, DocumentModel document, boolean removeFiltered) { return getActions(createActionContext(document), category, removeFiltered, false); } @Override public List<Action> getActionsList(String category) { return getActions(createActionContext(), category, true, false); } @Override public List<Action> getAllActions(String category) { return actionManager.getAllActions(category); } protected ActionContext createActionContext() { return actionContextProvider.createActionContext(); } protected ActionContext createActionContext(DocumentModel document) { return actionContextProvider.createActionContext(document); } @Override public Action getAction(String actionId, boolean removeFiltered) { return getAction(createActionContext(), actionId, removeFiltered, false); } @Override public Action getActionForDocument(String actionId, DocumentModel document, boolean removeFiltered) { return getAction(createActionContext(document), actionId, removeFiltered, false); } @Override public Action getAction(String actionId, ActionContext context, boolean removeFiltered) { return getAction(context, actionId, removeFiltered, false); } @Override public boolean checkFilter(String filterId) { return actionManager.checkFilter(filterId, createActionContext()); } // tabs management protected Action getDefaultTab(String category) { if (DEFAULT_TABS_CATEGORY.equals(category)) { if (getTabsList() == null) { return null; } try { return tabsActionsList.get(0); } catch (IndexOutOfBoundsException e) { return null; } } else { // check if it's a subtab if (subTabsCategory != null && subTabsCategory.equals(category)) { if (getSubTabsList() == null) { return null; } try { return subTabsActionsList.get(0); } catch (IndexOutOfBoundsException e) { return null; } } // retrieve actions in given category and take the first one found List<Action> actions = getActionsList(category, createActionContext()); if (actions != null && actions.size() > 0) { // make sure selection event is sent Action action = actions.get(0); setCurrentTabAction(category, action); return action; } return null; } } @Override public Action getCurrentTabAction(String category) { Action action = currentTabActions.getCurrentTabAction(category); if (action == null) { // return default action action = getDefaultTab(category); } return action; } @Override public void setCurrentTabAction(String category, Action tabAction) { currentTabActions.setCurrentTabAction(category, tabAction); // additional cleanup of this cache if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { resetSubTabs(); } } @Override public Action getCurrentSubTabAction(String parentActionId) { return getCurrentTabAction(TabActionsSelection.getSubTabCategory(parentActionId)); } @Override public String getCurrentTabId(String category) { Action action = getCurrentTabAction(category); if (action != null) { return action.getId(); } return null; } @Override public boolean hasCurrentTabId(String category) { if (currentTabActions.getCurrentTabAction(category) == null) { return false; } return true; } @Override public void setCurrentTabId(String category, String tabId, String... subTabIds) { currentTabActions.setCurrentTabId(actionManager, createActionContext(), category, tabId, subTabIds); // additional cleanup of this cache if (WebActions.DEFAULT_TABS_CATEGORY.equals(category)) { resetSubTabs(); } } @Override public String getCurrentTabIds() { return currentTabActions.getCurrentTabIds(); } @Override public void setCurrentTabIds(String tabIds) { currentTabActions.setCurrentTabIds(actionManager, createActionContext(), tabIds); // reset subtabs just in case resetSubTabs(); } @Override public void resetCurrentTabs() { currentTabActions.resetCurrentTabs(); } @Override public void resetCurrentTabs(String category) { currentTabActions.resetCurrentTabs(category); } // tabs management specific to the DEFAULT_TABS_CATEGORY @Override public void resetCurrentTab() { resetCurrentTabs(DEFAULT_TABS_CATEGORY); } protected void resetSubTabs() { subTabsCategory = null; subTabsActionsList = null; // make sure event context is cleared so that factory is called again Contexts.getEventContext().remove("subTabsActionsList"); Contexts.getEventContext().remove("currentSubTabAction"); } @Override @Observer(value = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, EventNames.LOCATION_SELECTION_CHANGED }, create = false) @BypassInterceptors public void resetTabList() { tabsActionsList = null; resetSubTabs(); resetCurrentTab(); // make sure event context is cleared so that factory is called again Contexts.getEventContext().remove("tabsActionsList"); Contexts.getEventContext().remove("currentTabAction"); } @Override @Factory(value = "tabsActionsList", scope = EVENT) public List<Action> getTabsList() { if (tabsActionsList == null) { tabsActionsList = getActionsList(DEFAULT_TABS_CATEGORY); } return tabsActionsList; } @Override @Factory(value = "subTabsActionsList", scope = EVENT) public List<Action> getSubTabsList() { if (subTabsActionsList == null) { String currentTabId = getCurrentTabId(); if (currentTabId != null) { subTabsCategory = TabActionsSelection.getSubTabCategory(currentTabId); subTabsActionsList = getActionsList(subTabsCategory); } } return subTabsActionsList; } @Override @Factory(value = "currentTabAction", scope = EVENT) public Action getCurrentTabAction() { return getCurrentTabAction(DEFAULT_TABS_CATEGORY); } @Override public void setCurrentTabAction(Action currentTabAction) { setCurrentTabAction(DEFAULT_TABS_CATEGORY, currentTabAction); } @Override @Factory(value = "currentSubTabAction", scope = EVENT) public Action getCurrentSubTabAction() { Action action = getCurrentTabAction(); if (action != null) { return getCurrentTabAction(TabActionsSelection.getSubTabCategory(action.getId())); } return null; } @Override public void setCurrentSubTabAction(Action tabAction) { if (tabAction != null) { String[] categories = tabAction.getCategories(); if (categories == null || categories.length == 0) { log.error("Cannot set subtab with id '" + tabAction.getId() + "' as this action does not hold any category"); return; } if (categories.length != 1) { log.error("Setting subtab with id '" + tabAction.getId() + "' with category '" + categories[0] + "': use webActions#setCurrentTabAction(action, category) to specify another category"); } setCurrentTabAction(categories[0], tabAction); } } @Override public String getCurrentTabId() { Action currentTab = getCurrentTabAction(); if (currentTab != null) { return currentTab.getId(); } return null; } @Override public void setCurrentTabId(String tabId) { if (tabId != null) { // do not reset tab when not set as this method // is used for compatibility in default url pattern setCurrentTabId(DEFAULT_TABS_CATEGORY, tabId); } } @Override public String getCurrentSubTabId() { Action currentSubTab = getCurrentSubTabAction(); if (currentSubTab != null) { return currentSubTab.getId(); } return null; } @Override public void setCurrentSubTabId(String tabId) { if (tabId != null) { // do not reset tab when not set as this method // is used for compatibility in default url pattern Action action = getCurrentTabAction(); if (action != null) { setCurrentTabId(TabActionsSelection.getSubTabCategory(action.getId()), tabId); } } } // navigation API @Override public String setCurrentTabAndNavigate(String currentTabActionId) { return setCurrentTabAndNavigate(navigationContext.getCurrentDocument(), currentTabActionId); } @Override public String setCurrentTabAndNavigate(DocumentModel document, String currentTabActionId) { // navigate first because it will reset the tabs list String viewId = null; try { viewId = navigationContext.navigateToDocument(document); } catch (NuxeoException e) { log.error("Failed to navigate to " + document, e); } // force creation of new actions if needed getTabsList(); // set current tab setCurrentTabId(currentTabActionId); return viewId; } @Override @Factory(value = "useAjaxTabs", scope = ScopeType.SESSION) public boolean useAjaxTabs() { ConfigurationService configurationService = Framework.getService(ConfigurationService.class); if (configurationService.isBooleanPropertyTrue(AJAX_TAB_PROPERTY)) { return canUseAjaxTabs(); } return false; } @Override @Factory(value = "canUseAjaxTabs", scope = ScopeType.SESSION) public boolean canUseAjaxTabs() { FacesContext context = FacesContext.getCurrentInstance(); ExternalContext econtext = context.getExternalContext(); HttpServletRequest request = (HttpServletRequest) econtext.getRequest(); String ua = request.getHeader("User-Agent"); return UserAgentMatcher.isHistoryPushStateSupported(ua); } /** * Returns true if configuration property to remove optimizations around actions (for compatibility) has been * enabled. * * @since 8.2 */ @Factory(value = "removeActionOptims", scope = ScopeType.SESSION) public boolean removeActionOptims() { ConfigurationService cs = Framework.getService(ConfigurationService.class); return cs.isBooleanPropertyTrue("nuxeo.jsf.actions.removeActionOptims"); } @Observer(value = { EventNames.FLUSH_EVENT }, create = false) @BypassInterceptors public void onHotReloadFlush() { // reset above caches Context seamContext = Contexts.getSessionContext(); seamContext.remove("useAjaxTabs"); seamContext.remove("canUseAjaxTabs"); } }