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}