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