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     * Attribute that provides a hint for action ordering.
106     * <p>
107     * :XXX: Action ordering remains a problem. We will continue to use the existing strategy of, by default, ordering
108     * actions by specificity of registration and order of definition.
109     */
110    @XNode("@order")
111    protected int order = 0;
112
113    @XNodeList(value = "category", type = String[].class, componentType = String.class)
114    protected String[] categories = EMPTY_CATEGORIES;
115
116    // 'action -> filter(s)' association
117
118    @XNodeList(value = "filter-id", type = ArrayList.class, componentType = String.class)
119    protected List<String> filterIds;
120
121    @XNodeList(value = "filter", type = ActionFilter[].class, componentType = DefaultActionFilter.class)
122    protected ActionFilter[] filters;
123
124    public Action() {
125    }
126
127    public Action(String id, String[] categories) {
128        this.id = id;
129        this.categories = categories;
130    }
131
132    /**
133     * Returns true if the enabled element was set on the descriptor, useful for merging.
134     *
135     * @since 5.8
136     */
137    public boolean isEnableSet() {
138        return enabled != null;
139    }
140
141    public boolean isEnabled() {
142        return enabled == null || Boolean.TRUE.equals(enabled);
143    }
144
145    public void setEnabled(boolean enabled) {
146        this.enabled = Boolean.valueOf(enabled);
147    }
148
149    protected String getStringProperty(String prop) {
150        Map<String, Serializable> props = getProperties();
151        if (props != null && props.containsKey(prop)) {
152            Object obj = props.get(prop);
153            if (obj instanceof String) {
154                return (String) obj;
155            }
156        }
157        return null;
158    }
159
160    public String getLabel() {
161        if (label == null) {
162            return getStringProperty("label");
163        }
164        return label;
165    }
166
167    public void setLabel(String label) {
168        this.label = label;
169    }
170
171    public String getIcon() {
172        if (icon == null) {
173            return getStringProperty("icon");
174        }
175        return icon;
176    }
177
178    public void setIcon(String icon) {
179        this.icon = icon;
180    }
181
182    /**
183     * Returns the link for this action.
184     * <p>
185     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "link".
186     */
187    public String getLink() {
188        if (link == null) {
189            return getStringProperty("link");
190        }
191        return link;
192    }
193
194    @XNode("@link")
195    public void setLink(String link) {
196        if (link != null) {
197            this.link = Framework.expandVars(link);
198        }
199    }
200
201    public String[] getCategories() {
202        return categories;
203    }
204
205    /**
206     * Returns the categories as a list.
207     *
208     * @since 7.2
209     */
210    public List<String> getCategoryList() {
211        if (categories == null) {
212            return null;
213        }
214        return Arrays.asList(categories);
215    }
216
217    public String getId() {
218        return id;
219    }
220
221    @Override
222    public String toString() {
223        return id;
224    }
225
226    /**
227     * Returns the action order.
228     *
229     * @return the action order as an integer value
230     */
231    public int getOrder() {
232        return order;
233    }
234
235    /**
236     * Sets the order of the action.
237     *
238     * @param order order of the action
239     */
240    public void setOrder(int order) {
241        this.order = order;
242    }
243
244    public int compareTo(Action anotherAction) {
245        int cmp = order - anotherAction.order;
246        if (cmp == 0) {
247            // make sure we have a deterministic sort
248            cmp = id.compareTo(anotherAction.id);
249        }
250        return cmp;
251    }
252
253    public List<String> getFilterIds() {
254        return filterIds;
255    }
256
257    public void setFilterIds(List<String> filterIds) {
258        this.filterIds = filterIds;
259    }
260
261    public ActionFilter[] getFilters() {
262        return filters;
263    }
264
265    public void setFilters(ActionFilter[] filters) {
266        this.filters = filters;
267    }
268
269    public void setCategories(String[] categories) {
270        this.categories = categories;
271    }
272
273    /**
274     * @deprecated since 5.6: useless now that EL expressions support parameters
275     */
276    @Deprecated
277    @SuppressWarnings("rawtypes")
278    public Class[] getLinkParams() {
279        return linkParams;
280    }
281
282    /**
283     * @deprecated since 5.6: useless now that EL expressions support parameters
284     */
285    @Deprecated
286    public void setLinkParams(Class<?>[] linkParams) {
287        this.linkParams = linkParams;
288    }
289
290    /**
291     * Returns the confirm javascript for this element.
292     * <p>
293     * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm".
294     */
295    public String getConfirm() {
296        if (confirm == null) {
297            String conf = getStringProperty("confirm");
298            if (conf == null) {
299                conf = "";
300            }
301            return conf;
302        } else {
303            return confirm;
304        }
305    }
306
307    public void setConfirm(String confirm) {
308        this.confirm = confirm;
309    }
310
311    public boolean getAvailable() {
312        return available;
313    }
314
315    public void setAvailable(boolean available) {
316        this.available = available;
317    }
318
319    public String getHelp() {
320        if (help == null) {
321            String conf = getStringProperty("help");
322            if (conf == null) {
323                conf = "";
324            }
325            return conf;
326        } else {
327            return help;
328        }
329    }
330
331    public void setHelp(String title) {
332        help = title;
333    }
334
335    public boolean isImmediate() {
336        if (immediate == null) {
337            Map<String, Serializable> props = getProperties();
338            if (props != null && props.containsKey("immediate")) {
339                Object obj = props.get("immediate");
340                if (obj instanceof String) {
341                    return Boolean.valueOf((String) obj).booleanValue();
342                } else if (obj instanceof Boolean) {
343                    return ((Boolean) obj).booleanValue();
344                }
345            }
346            return false;
347        }
348        return immediate.booleanValue();
349    }
350
351    public void setImmediate(boolean immediate) {
352        this.immediate = Boolean.valueOf(immediate);
353    }
354
355    /**
356     * @since 5.6
357     */
358    public String getType() {
359        return type;
360    }
361
362    /**
363     * @since 5.6
364     */
365    public void setType(String type) {
366        this.type = type;
367    }
368
369    /**
370     * @since 5.6
371     */
372    public ActionPropertiesDescriptor getPropertiesDescriptor() {
373        return properties;
374    }
375
376    /**
377     * @since 5.6
378     */
379    public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) {
380        this.properties = properties;
381        this.propertiesCache = null;
382    }
383
384    /**
385     * Sets local properties programatically
386     *
387     * @since 5.6
388     */
389    public void setProperties(Map<String, Serializable> localProperties) {
390        this.localProperties = localProperties;
391        this.propertiesCache = null;
392    }
393
394    /**
395     * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors.
396     *
397     * @since 5.6
398     */
399    public Map<String, Serializable> getProperties() {
400        if (propertiesCache == null) {
401            propertiesCache = new HashMap<String, Serializable>();
402            if (properties != null) {
403                propertiesCache.putAll(properties.getAllProperties());
404            }
405            if (localProperties != null) {
406                propertiesCache.putAll(localProperties);
407            }
408        }
409        return propertiesCache;
410    }
411
412    /**
413     * @since 5.6
414     */
415    public void setAccessKey(String accessKey) {
416        this.accessKey = accessKey;
417    }
418
419    /**
420     * @since 5.6
421     */
422    public String getAccessKey() {
423        if (accessKey == null) {
424            return getStringProperty("accessKey");
425        }
426        return accessKey;
427    }
428
429    @Override
430    public boolean equals(Object other) {
431        if (this == other) {
432            return true;
433        }
434        if (other == null) {
435            return false;
436        }
437        if (!(other instanceof Action)) {
438            return false;
439        }
440        Action otherAction = (Action) other;
441        return id == null ? otherAction.id == null : id.equals(otherAction.id);
442    }
443
444    @Override
445    public int hashCode() {
446        return id == null ? 0 : id.hashCode();
447    }
448
449    @Override
450    public Action clone() {
451        Action clone = new Action();
452        clone.id = id;
453        clone.link = link;
454        if (linkParams != null) {
455            clone.linkParams = linkParams.clone();
456        }
457        clone.enabled = enabled;
458        clone.label = label;
459        clone.icon = icon;
460        clone.confirm = confirm;
461        clone.help = help;
462        clone.immediate = immediate;
463        clone.accessKey = accessKey;
464        clone.type = type;
465        if (properties != null) {
466            clone.properties = properties.clone();
467        }
468        clone.available = available;
469        clone.order = order;
470        if (categories != null) {
471            clone.categories = categories.clone();
472        }
473        if (filterIds != null) {
474            clone.filterIds = new ArrayList<String>(filterIds);
475        }
476        if (filters != null) {
477            clone.filters = new ActionFilter[filters.length];
478            for (int i = 0; i < filters.length; i++) {
479                clone.filters[i] = filters[i].clone();
480            }
481        }
482        return clone;
483    }
484
485}