001/*
002 * (C) Copyright 2006-2016 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 *     Nuxeo - initial API and implementation
018 */
019package org.nuxeo.ecm.platform.actions;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.nuxeo.common.xmap.annotation.XNode;
029import org.nuxeo.common.xmap.annotation.XNodeList;
030import org.nuxeo.common.xmap.annotation.XObject;
031import org.nuxeo.runtime.api.Framework;
032
033/**
034 * Descriptor for action.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038@XObject("action")
039public class Action implements Serializable, Cloneable, Comparable<Action> {
040
041    public static final String[] EMPTY_CATEGORIES = new String[0];
042
043    private static final long serialVersionUID = 1L;
044
045    @XNode("@id")
046    protected String id = "";
047
048    protected String link = null;
049
050    @XNode("@enabled")
051    protected Boolean enabled;
052
053    @XNode("@label")
054    protected String label;
055
056    @XNode("@icon")
057    protected String icon;
058
059    @XNode("@confirm")
060    protected String confirm;
061
062    @XNode("@help")
063    protected String help;
064
065    @XNode("@immediate")
066    protected Boolean immediate;
067
068    @XNode("@accessKey")
069    protected String accessKey;
070
071    /**
072     * @since 5.6
073     */
074    @XNode("@type")
075    protected String type = null;
076
077    /**
078     * @since 5.6
079     */
080    @XNode("properties")
081    protected ActionPropertiesDescriptor properties;
082
083    /**
084     * Extra set of properties to be used by API, when creating actions on the fly without contributions to the service.
085     *
086     * @since 5.6
087     */
088    protected Map<String, Serializable> localProperties;
089
090    /**
091     * @since 5.6
092     */
093    protected Map<String, Serializable> propertiesCache;
094
095    protected boolean available = true;
096
097    /**
098     * @since 8.2
099     */
100    protected boolean filtered = false;
101
102    /**
103     * Attribute that provides a hint for action ordering.
104     * <p>
105     * :XXX: Action ordering remains a problem. We will continue to use the existing strategy of, by default, ordering
106     * actions by specificity of registration and order of definition.
107     */
108    @XNode("@order")
109    protected int order = 0;
110
111    @XNodeList(value = "category", type = String[].class, componentType = String.class)
112    protected String[] categories = EMPTY_CATEGORIES;
113
114    // 'action -> filter(s)' association
115
116    @XNodeList(value = "filter-id", type = ArrayList.class, componentType = String.class)
117    protected List<String> filterIds;
118
119    @XNodeList(value = "filter", type = ActionFilter[].class, componentType = DefaultActionFilter.class)
120    protected ActionFilter[] filters;
121
122    public Action() {
123    }
124
125    public Action(String id, String[] categories) {
126        this.id = id;
127        this.categories = categories;
128    }
129
130    /**
131     * Returns true if the enabled element was set on the descriptor, useful for merging.
132     *
133     * @since 5.8
134     */
135    public boolean isEnableSet() {
136        return enabled != null;
137    }
138
139    public boolean isEnabled() {
140        return enabled == null || Boolean.TRUE.equals(enabled);
141    }
142
143    public void setEnabled(boolean enabled) {
144        this.enabled = Boolean.valueOf(enabled);
145    }
146
147    protected String getStringProperty(String prop) {
148        Map<String, Serializable> props = getProperties();
149        if (props != null && props.containsKey(prop)) {
150            Object obj = props.get(prop);
151            if (obj instanceof String) {
152                return (String) obj;
153            }
154        }
155        return null;
156    }
157
158    public String getLabel() {
159        if (label == null) {
160            return getStringProperty("label");
161        }
162        return label;
163    }
164
165    public void setLabel(String label) {
166        this.label = label;
167    }
168
169    public String getIcon() {
170        if (icon == null) {
171            return getStringProperty("icon");
172        }
173        return icon;
174    }
175
176    public void setIcon(String icon) {
177        this.icon = icon;
178    }
179
180    /**
181     * Returns the link for this action.
182     * <p>
183     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "link".
184     */
185    public String getLink() {
186        if (link == null) {
187            return getStringProperty("link");
188        }
189        return link;
190    }
191
192    @XNode("@link")
193    public void setLink(String link) {
194        if (link != null) {
195            this.link = Framework.expandVars(link);
196        }
197    }
198
199    public String[] getCategories() {
200        return categories;
201    }
202
203    /**
204     * Returns the categories as a list.
205     *
206     * @since 7.2
207     */
208    public List<String> getCategoryList() {
209        if (categories == null) {
210            return null;
211        }
212        return Arrays.asList(categories);
213    }
214
215    public String getId() {
216        return id;
217    }
218
219    @Override
220    public String toString() {
221        return id;
222    }
223
224    /**
225     * Returns the action order.
226     *
227     * @return the action order as an integer value
228     */
229    public int getOrder() {
230        return order;
231    }
232
233    /**
234     * Sets the order of the action.
235     *
236     * @param order order of the action
237     */
238    public void setOrder(int order) {
239        this.order = order;
240    }
241
242    public int compareTo(Action anotherAction) {
243        int cmp = order - anotherAction.order;
244        if (cmp == 0) {
245            // make sure we have a deterministic sort
246            cmp = id.compareTo(anotherAction.id);
247        }
248        return cmp;
249    }
250
251    public List<String> getFilterIds() {
252        return filterIds;
253    }
254
255    public void setFilterIds(List<String> filterIds) {
256        this.filterIds = filterIds;
257    }
258
259    public ActionFilter[] getFilters() {
260        return filters;
261    }
262
263    public void setFilters(ActionFilter[] filters) {
264        this.filters = filters;
265    }
266
267    public void setCategories(String[] categories) {
268        this.categories = categories;
269    }
270
271    /**
272     * Returns the confirm javascript for this element.
273     * <p>
274     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm".
275     */
276    public String getConfirm() {
277        if (confirm == null) {
278            String conf = getStringProperty("confirm");
279            if (conf == null) {
280                conf = "";
281            }
282            return conf;
283        } else {
284            return confirm;
285        }
286    }
287
288    public void setConfirm(String confirm) {
289        this.confirm = confirm;
290    }
291
292    public boolean getAvailable() {
293        return available;
294    }
295
296    public void setAvailable(boolean available) {
297        this.available = available;
298    }
299
300    /**
301     * @since 8.2
302     */
303    public boolean isFiltered() {
304        return filtered;
305    }
306
307    /**
308     * @since 8.2
309     */
310    public void setFiltered(boolean filtered) {
311        this.filtered = filtered;
312    }
313
314    public String getHelp() {
315        if (help == null) {
316            String conf = getStringProperty("help");
317            if (conf == null) {
318                conf = "";
319            }
320            return conf;
321        } else {
322            return help;
323        }
324    }
325
326    public void setHelp(String title) {
327        help = title;
328    }
329
330    public boolean isImmediate() {
331        if (immediate == null) {
332            Map<String, Serializable> props = getProperties();
333            if (props != null && props.containsKey("immediate")) {
334                Object obj = props.get("immediate");
335                if (obj instanceof String) {
336                    return Boolean.valueOf((String) obj).booleanValue();
337                } else if (obj instanceof Boolean) {
338                    return ((Boolean) obj).booleanValue();
339                }
340            }
341            return false;
342        }
343        return immediate.booleanValue();
344    }
345
346    public void setImmediate(boolean immediate) {
347        this.immediate = Boolean.valueOf(immediate);
348    }
349
350    /**
351     * @since 5.6
352     */
353    public String getType() {
354        return type;
355    }
356
357    /**
358     * @since 5.6
359     */
360    public void setType(String type) {
361        this.type = type;
362    }
363
364    /**
365     * @since 5.6
366     */
367    public ActionPropertiesDescriptor getPropertiesDescriptor() {
368        return properties;
369    }
370
371    /**
372     * @since 5.6
373     */
374    public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) {
375        this.properties = properties;
376        this.propertiesCache = null;
377    }
378
379    /**
380     * Sets local properties programatically
381     *
382     * @since 5.6
383     */
384    public void setProperties(Map<String, Serializable> localProperties) {
385        this.localProperties = localProperties;
386        this.propertiesCache = null;
387    }
388
389    /**
390     * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors.
391     *
392     * @since 5.6
393     */
394    public Map<String, Serializable> getProperties() {
395        if (propertiesCache == null) {
396            propertiesCache = new HashMap<>();
397            if (properties != null) {
398                propertiesCache.putAll(properties.getAllProperties());
399            }
400            if (localProperties != null) {
401                propertiesCache.putAll(localProperties);
402            }
403        }
404        return propertiesCache;
405    }
406
407    /**
408     * @since 5.6
409     */
410    public void setAccessKey(String accessKey) {
411        this.accessKey = accessKey;
412    }
413
414    /**
415     * @since 5.6
416     */
417    public String getAccessKey() {
418        if (accessKey == null) {
419            return getStringProperty("accessKey");
420        }
421        return accessKey;
422    }
423
424    @Override
425    public boolean equals(Object other) {
426        if (this == other) {
427            return true;
428        }
429        if (other == null) {
430            return false;
431        }
432        if (!(other instanceof Action)) {
433            return false;
434        }
435        Action otherAction = (Action) other;
436        return id == null ? otherAction.id == null : id.equals(otherAction.id);
437    }
438
439    @Override
440    public int hashCode() {
441        return id == null ? 0 : id.hashCode();
442    }
443
444    @Override
445    public Action clone() {
446        Action clone = new Action();
447        clone.id = id;
448        clone.link = link;
449        clone.enabled = enabled;
450        clone.label = label;
451        clone.icon = icon;
452        clone.confirm = confirm;
453        clone.help = help;
454        clone.immediate = immediate;
455        clone.accessKey = accessKey;
456        clone.type = type;
457        if (properties != null) {
458            clone.properties = properties.clone();
459        }
460        clone.available = available;
461        clone.filtered = filtered;
462        clone.order = order;
463        if (categories != null) {
464            clone.categories = categories.clone();
465        }
466        if (filterIds != null) {
467            clone.filterIds = new ArrayList<>(filterIds);
468        }
469        if (filters != null) {
470            clone.filters = new ActionFilter[filters.length];
471            for (int i = 0; i < filters.length; i++) {
472                clone.filters[i] = filters[i].clone();
473            }
474        }
475        return clone;
476    }
477
478}