001/*
002 * (C) Copyright 2006-2012 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: Action.java 28512 2008-01-06 11:52:28Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.actions;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.nuxeo.common.xmap.annotation.XNode;
030import org.nuxeo.common.xmap.annotation.XNodeList;
031import org.nuxeo.common.xmap.annotation.XObject;
032import org.nuxeo.runtime.api.Framework;
033
034/**
035 * Descriptor for action.
036 *
037 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
038 */
039@XObject("action")
040public class Action implements Serializable, Cloneable, Comparable<Action> {
041
042    public static final String[] EMPTY_CATEGORIES = new String[0];
043
044    private static final long serialVersionUID = 1L;
045
046    @XNode("@id")
047    protected String id = "";
048
049    protected String link = null;
050
051    @Deprecated
052    @XNodeList(value = "link-params/param", type = Class[].class, componentType = Class.class)
053    private Class<?>[] linkParams;
054
055    @XNode("@enabled")
056    protected Boolean enabled;
057
058    @XNode("@label")
059    protected String label;
060
061    @XNode("@icon")
062    protected String icon;
063
064    @XNode("@confirm")
065    protected String confirm;
066
067    @XNode("@help")
068    protected String help;
069
070    @XNode("@immediate")
071    protected Boolean immediate;
072
073    @XNode("@accessKey")
074    protected String accessKey;
075
076    /**
077     * @since 5.6
078     */
079    @XNode("@type")
080    protected String type = null;
081
082    /**
083     * @since 5.6
084     */
085    @XNode("properties")
086    protected ActionPropertiesDescriptor properties;
087
088    /**
089     * Extra set of properties to be used by API, when creating actions on the fly without contributions to the service.
090     *
091     * @since 5.6
092     */
093    protected Map<String, Serializable> localProperties;
094
095    /**
096     * @since 5.6
097     */
098    protected Map<String, Serializable> propertiesCache;
099
100    protected boolean available = true;
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     * @deprecated since 5.6: useless now that EL expressions support parameters
273     */
274    @Deprecated
275    @SuppressWarnings("rawtypes")
276    public Class[] getLinkParams() {
277        return linkParams;
278    }
279
280    /**
281     * @deprecated since 5.6: useless now that EL expressions support parameters
282     */
283    @Deprecated
284    public void setLinkParams(Class<?>[] linkParams) {
285        this.linkParams = linkParams;
286    }
287
288    /**
289     * Returns the confirm javascript for this element.
290     * <p>
291     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm".
292     */
293    public String getConfirm() {
294        if (confirm == null) {
295            String conf = getStringProperty("confirm");
296            if (conf == null) {
297                conf = "";
298            }
299            return conf;
300        } else {
301            return confirm;
302        }
303    }
304
305    public void setConfirm(String confirm) {
306        this.confirm = confirm;
307    }
308
309    public boolean getAvailable() {
310        return available;
311    }
312
313    public void setAvailable(boolean available) {
314        this.available = available;
315    }
316
317    public String getHelp() {
318        if (help == null) {
319            String conf = getStringProperty("help");
320            if (conf == null) {
321                conf = "";
322            }
323            return conf;
324        } else {
325            return help;
326        }
327    }
328
329    public void setHelp(String title) {
330        help = title;
331    }
332
333    public boolean isImmediate() {
334        if (immediate == null) {
335            Map<String, Serializable> props = getProperties();
336            if (props != null && props.containsKey("immediate")) {
337                Object obj = props.get("immediate");
338                if (obj instanceof String) {
339                    return Boolean.valueOf((String) obj).booleanValue();
340                } else if (obj instanceof Boolean) {
341                    return ((Boolean) obj).booleanValue();
342                }
343            }
344            return false;
345        }
346        return immediate.booleanValue();
347    }
348
349    public void setImmediate(boolean immediate) {
350        this.immediate = Boolean.valueOf(immediate);
351    }
352
353    /**
354     * @since 5.6
355     */
356    public String getType() {
357        return type;
358    }
359
360    /**
361     * @since 5.6
362     */
363    public void setType(String type) {
364        this.type = type;
365    }
366
367    /**
368     * @since 5.6
369     */
370    public ActionPropertiesDescriptor getPropertiesDescriptor() {
371        return properties;
372    }
373
374    /**
375     * @since 5.6
376     */
377    public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) {
378        this.properties = properties;
379        this.propertiesCache = null;
380    }
381
382    /**
383     * Sets local properties programatically
384     *
385     * @since 5.6
386     */
387    public void setProperties(Map<String, Serializable> localProperties) {
388        this.localProperties = localProperties;
389        this.propertiesCache = null;
390    }
391
392    /**
393     * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors.
394     *
395     * @since 5.6
396     */
397    public Map<String, Serializable> getProperties() {
398        if (propertiesCache == null) {
399            propertiesCache = new HashMap<String, Serializable>();
400            if (properties != null) {
401                propertiesCache.putAll(properties.getAllProperties());
402            }
403            if (localProperties != null) {
404                propertiesCache.putAll(localProperties);
405            }
406        }
407        return propertiesCache;
408    }
409
410    /**
411     * @since 5.6
412     */
413    public void setAccessKey(String accessKey) {
414        this.accessKey = accessKey;
415    }
416
417    /**
418     * @since 5.6
419     */
420    public String getAccessKey() {
421        if (accessKey == null) {
422            return getStringProperty("accessKey");
423        }
424        return accessKey;
425    }
426
427    @Override
428    public boolean equals(Object other) {
429        if (this == other) {
430            return true;
431        }
432        if (other == null) {
433            return false;
434        }
435        if (!(other instanceof Action)) {
436            return false;
437        }
438        Action otherAction = (Action) other;
439        return id == null ? otherAction.id == null : id.equals(otherAction.id);
440    }
441
442    @Override
443    public int hashCode() {
444        return id == null ? 0 : id.hashCode();
445    }
446
447    @Override
448    public Action clone() {
449        Action clone = new Action();
450        clone.id = id;
451        clone.link = link;
452        if (linkParams != null) {
453            clone.linkParams = linkParams.clone();
454        }
455        clone.enabled = enabled;
456        clone.label = label;
457        clone.icon = icon;
458        clone.confirm = confirm;
459        clone.help = help;
460        clone.immediate = immediate;
461        clone.accessKey = accessKey;
462        clone.type = type;
463        if (properties != null) {
464            clone.properties = properties.clone();
465        }
466        clone.available = available;
467        clone.order = order;
468        if (categories != null) {
469            clone.categories = categories.clone();
470        }
471        if (filterIds != null) {
472            clone.filterIds = new ArrayList<String>(filterIds);
473        }
474        if (filters != null) {
475            clone.filters = new ActionFilter[filters.length];
476            for (int i = 0; i < filters.length; i++) {
477                clone.filters[i] = filters[i].clone();
478            }
479        }
480        return clone;
481    }
482
483}