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    @Override
243    public int compareTo(Action anotherAction) {
244        int cmp = order - anotherAction.order;
245        if (cmp == 0) {
246            // make sure we have a deterministic sort
247            cmp = id.compareTo(anotherAction.id);
248        }
249        return cmp;
250    }
251
252    public List<String> getFilterIds() {
253        return filterIds;
254    }
255
256    public void setFilterIds(List<String> filterIds) {
257        this.filterIds = filterIds;
258    }
259
260    public ActionFilter[] getFilters() {
261        return filters;
262    }
263
264    public void setFilters(ActionFilter[] filters) {
265        this.filters = filters;
266    }
267
268    public void setCategories(String[] categories) {
269        this.categories = categories;
270    }
271
272    /**
273     * Returns the confirm javascript for this element.
274     * <p>
275     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm".
276     */
277    public String getConfirm() {
278        if (confirm == null) {
279            String conf = getStringProperty("confirm");
280            if (conf == null) {
281                conf = "";
282            }
283            return conf;
284        } else {
285            return confirm;
286        }
287    }
288
289    public void setConfirm(String confirm) {
290        this.confirm = confirm;
291    }
292
293    public boolean getAvailable() {
294        return available;
295    }
296
297    public void setAvailable(boolean available) {
298        this.available = available;
299    }
300
301    /**
302     * @since 8.2
303     */
304    public boolean isFiltered() {
305        return filtered;
306    }
307
308    /**
309     * @since 8.2
310     */
311    public void setFiltered(boolean filtered) {
312        this.filtered = filtered;
313    }
314
315    public String getHelp() {
316        if (help == null) {
317            String conf = getStringProperty("help");
318            if (conf == null) {
319                conf = "";
320            }
321            return conf;
322        } else {
323            return help;
324        }
325    }
326
327    public void setHelp(String title) {
328        help = title;
329    }
330
331    public boolean isImmediate() {
332        if (immediate == null) {
333            Map<String, Serializable> props = getProperties();
334            if (props != null && props.containsKey("immediate")) {
335                Object obj = props.get("immediate");
336                if (obj instanceof String) {
337                    return Boolean.valueOf((String) obj).booleanValue();
338                } else if (obj instanceof Boolean) {
339                    return ((Boolean) obj).booleanValue();
340                }
341            }
342            return false;
343        }
344        return immediate.booleanValue();
345    }
346
347    public void setImmediate(boolean immediate) {
348        this.immediate = Boolean.valueOf(immediate);
349    }
350
351    /**
352     * @since 5.6
353     */
354    public String getType() {
355        return type;
356    }
357
358    /**
359     * @since 5.6
360     */
361    public void setType(String type) {
362        this.type = type;
363    }
364
365    /**
366     * @since 5.6
367     */
368    public ActionPropertiesDescriptor getPropertiesDescriptor() {
369        return properties;
370    }
371
372    /**
373     * @since 5.6
374     */
375    public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) {
376        this.properties = properties;
377        this.propertiesCache = null;
378    }
379
380    /**
381     * Sets local properties programatically
382     *
383     * @since 5.6
384     */
385    public void setProperties(Map<String, Serializable> localProperties) {
386        this.localProperties = localProperties;
387        this.propertiesCache = null;
388    }
389
390    /**
391     * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors.
392     *
393     * @since 5.6
394     */
395    public Map<String, Serializable> getProperties() {
396        if (propertiesCache == null) {
397            propertiesCache = new HashMap<>();
398            if (properties != null) {
399                propertiesCache.putAll(properties.getAllProperties());
400            }
401            if (localProperties != null) {
402                propertiesCache.putAll(localProperties);
403            }
404        }
405        return propertiesCache;
406    }
407
408    /**
409     * @since 5.6
410     */
411    public void setAccessKey(String accessKey) {
412        this.accessKey = accessKey;
413    }
414
415    /**
416     * @since 5.6
417     */
418    public String getAccessKey() {
419        if (accessKey == null) {
420            return getStringProperty("accessKey");
421        }
422        return accessKey;
423    }
424
425    @Override
426    public boolean equals(Object other) {
427        if (this == other) {
428            return true;
429        }
430        if (other == null) {
431            return false;
432        }
433        if (!(other instanceof Action)) {
434            return false;
435        }
436        Action otherAction = (Action) other;
437        return id == null ? otherAction.id == null : id.equals(otherAction.id);
438    }
439
440    @Override
441    public int hashCode() {
442        return id == null ? 0 : id.hashCode();
443    }
444
445    @Override
446    public Action clone() {
447        Action clone = new Action();
448        clone.id = id;
449        clone.link = link;
450        clone.enabled = enabled;
451        clone.label = label;
452        clone.icon = icon;
453        clone.confirm = confirm;
454        clone.help = help;
455        clone.immediate = immediate;
456        clone.accessKey = accessKey;
457        clone.type = type;
458        if (properties != null) {
459            clone.properties = properties.clone();
460        }
461        clone.available = available;
462        clone.filtered = filtered;
463        clone.order = order;
464        if (categories != null) {
465            clone.categories = categories.clone();
466        }
467        if (filterIds != null) {
468            clone.filterIds = new ArrayList<>(filterIds);
469        }
470        if (filters != null) {
471            clone.filters = new ActionFilter[filters.length];
472            for (int i = 0; i < filters.length; i++) {
473                clone.filters[i] = filters[i].clone();
474            }
475        }
476        return clone;
477    }
478
479}