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