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}