001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
016 *
017 * $Id: ActionRegistry.java 20637 2007-06-17 12:37:03Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.actions;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.lang.StringUtils;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034
035/**
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public class ActionRegistry implements Serializable {
039
040    private static final Log log = LogFactory.getLog(ActionRegistry.class);
041
042    private static final long serialVersionUID = 8425627293154848041L;
043
044    private final Map<String, Action> actions;
045
046    private final Map<String, List<String>> categories;
047
048    private List<TypeCompatibility> typeCategoryRelations;
049
050    public ActionRegistry() {
051        actions = new HashMap<String, Action>();
052        categories = new HashMap<String, List<String>>();
053        typeCategoryRelations = new ArrayList<TypeCompatibility>();
054    }
055
056    public synchronized void addAction(Action action) {
057        String id = action.getId();
058        if (log.isDebugEnabled()) {
059            if (actions.containsKey(id)) {
060                log.debug("Overriding action: " + action);
061            } else {
062                log.debug("Registering action: " + action);
063            }
064        }
065        // add a default label if not set
066        if (action.getLabel() == null) {
067            action.setLabel(action.getId());
068        }
069        actions.put(id, action);
070        for (String category : action.getCategories()) {
071            List<String> acts = categories.get(category);
072            if (acts == null) {
073                acts = new ArrayList<String>();
074            }
075            if (!acts.contains(id)) {
076                acts.add(id);
077            }
078            categories.put(category, acts);
079        }
080    }
081
082    public synchronized Action removeAction(String id) {
083        if (log.isDebugEnabled()) {
084            log.debug("Unregistering action: " + id);
085        }
086
087        Action action = actions.remove(id);
088        if (action != null) {
089            for (String category : action.getCategories()) {
090                List<String> acts = categories.get(category);
091                if (acts != null) {
092                    acts.remove(id);
093                }
094            }
095        }
096        return action;
097    }
098
099    public synchronized Collection<Action> getActions() {
100        return Collections.unmodifiableCollection(sortActions(actions.values()));
101    }
102
103    public List<Action> getActions(String category) {
104        List<Action> result = new LinkedList<Action>();
105        Collection<String> ids;
106        synchronized (this) {
107            ids = categories.get(category);
108        }
109        if (ids != null) {
110            for (String id : ids) {
111                Action action = actions.get(id);
112                if (action != null && action.isEnabled()) {
113                    // UI type action compat check
114                    Action finalAction = getClonedAction(action);
115                    applyCompatibility(category, finalAction);
116                    // return only enabled actions
117                    result.add(finalAction);
118                }
119            }
120        }
121        result = sortActions(result);
122        return result;
123    }
124
125    protected void applyCompatibility(Action finalAction) {
126        if (finalAction != null && finalAction.getType() == null) {
127            // iterate over all categories to apply compat
128            String[] cats = finalAction.getCategories();
129            if (cats != null) {
130                for (String cat : cats) {
131                    if (applyCompatibility(cat, finalAction)) {
132                        break;
133                    }
134                }
135            }
136        }
137    }
138
139    protected boolean applyCompatibility(String category, Action finalAction) {
140        if (finalAction != null && finalAction.getType() == null) {
141            for (TypeCompatibility compat : typeCategoryRelations) {
142                for (String compatCategory : compat.getCategories()) {
143                    if (StringUtils.equals(compatCategory, category)) {
144                        finalAction.setType(compat.getType());
145                        if (applyCustomCompatibility(compat.getType(), finalAction)) {
146                            return true;
147                        }
148                    }
149                }
150            }
151        }
152        return false;
153    }
154
155    /**
156     * Displays specific help messages for migration of actions.
157     *
158     * @since 6.0
159     */
160    protected boolean applyCustomCompatibility(String compatType, Action action) {
161        // 6.0 BBB: home/admin tab actions migrated to widgets
162        if ("admin_rest_document_link".equals(compatType) || "home_rest_document_link".equals(compatType)) {
163            boolean applied = false;
164            String link = action.getLink();
165            if (link != null && !link.startsWith("/")) {
166                action.setLink("/" + link);
167                applied = true;
168            }
169            if (applied) {
170                log.warn(String.format("Applied compatibility to action '%s', its configuration "
171                        + "should be reviewed: make sure the link references an " + "absolute path", action.getId()));
172                return true;
173            }
174        }
175        return false;
176    }
177
178    public synchronized Action getAction(String id) {
179        Action action = actions.get(id);
180        Action finalAction = getClonedAction(action);
181        applyCompatibility(finalAction);
182        return finalAction;
183    }
184
185    protected Action getClonedAction(Action action) {
186        if (action == null) {
187            return null;
188        }
189        return action.clone();
190    }
191
192    protected static List<Action> sortActions(Collection<Action> actions) {
193        List<Action> sortedActions = new ArrayList<Action>();
194        if (actions != null) {
195            sortedActions.addAll(actions);
196            Collections.sort(sortedActions);
197        }
198        return sortedActions;
199    }
200
201    public List<TypeCompatibility> getTypeCategoryRelations() {
202        return typeCategoryRelations;
203    }
204
205    public void setTypeCategoryRelations(List<TypeCompatibility> typeCategoryRelations) {
206        this.typeCategoryRelations = typeCategoryRelations;
207    }
208
209}