001/*
002 * (C) Copyright 2006-2007 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 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id: WidgetDescriptor.java 28478 2008-01-04 12:53:58Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.forms.layout.descriptors;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.common.xmap.XMap;
031import org.nuxeo.common.xmap.annotation.XContent;
032import org.nuxeo.common.xmap.annotation.XNode;
033import org.nuxeo.common.xmap.annotation.XNodeList;
034import org.nuxeo.common.xmap.annotation.XNodeMap;
035import org.nuxeo.common.xmap.annotation.XObject;
036import org.nuxeo.ecm.platform.forms.layout.api.BuiltinModes;
037import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
038import org.nuxeo.ecm.platform.forms.layout.api.RenderingInfo;
039import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
040import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
041import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOption;
042import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
043import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
044import org.w3c.dom.DocumentFragment;
045import org.w3c.dom.Element;
046import org.w3c.dom.Node;
047
048/**
049 * Widget definition descriptor.
050 *
051 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
052 */
053@XObject("widget")
054public class WidgetDescriptor {
055
056    private static final Log log = LogFactory.getLog(WidgetDescriptor.class);
057
058    @XNode("@name")
059    String name;
060
061    @XNode("@type")
062    String type;
063
064    /**
065     * @since 5.7.3
066     */
067    @XNode("@typeCategory")
068    String typeCategory;
069
070    @XNodeList(value = "fields/field", type = FieldDescriptor[].class, componentType = FieldDescriptor.class)
071    FieldDescriptor[] fields = new FieldDescriptor[0];
072
073    @XNodeMap(value = "widgetModes/mode", key = "@value", type = HashMap.class, componentType = String.class)
074    Map<String, String> modes = new HashMap<String, String>();
075
076    @XNodeMap(value = "labels/label", key = "@mode", type = HashMap.class, componentType = String.class)
077    Map<String, String> labels = new HashMap<String, String>();
078
079    @XNodeMap(value = "helpLabels/label", key = "@mode", type = HashMap.class, componentType = String.class)
080    Map<String, String> helpLabels = new HashMap<String, String>();
081
082    /**
083     * Defaults to true, contrary to {@link WidgetDefinition} interface, but kept as is for compatibility.
084     */
085    @XNode("translated")
086    boolean translated = true;
087
088    /**
089     * @since 5.6
090     * @deprecated since 5.7: use {@link #controls} instead, with name "handleLabels".
091     */
092    @Deprecated
093    @XNode("handlingLabels")
094    boolean handlingLabels = false;
095
096    @XNodeMap(value = "properties", key = "@mode", type = HashMap.class, componentType = PropertiesDescriptor.class)
097    Map<String, PropertiesDescriptor> properties = new HashMap<String, PropertiesDescriptor>();
098
099    @XNodeMap(value = "controls", key = "@mode", type = HashMap.class, componentType = ControlsDescriptor.class)
100    Map<String, ControlsDescriptor> controls = new HashMap<String, ControlsDescriptor>();
101
102    @XNodeMap(value = "properties", key = "@widgetMode", type = HashMap.class, componentType = PropertiesDescriptor.class)
103    Map<String, PropertiesDescriptor> widgetModeProperties = new HashMap<String, PropertiesDescriptor>();
104
105    @XNodeList(value = "subWidgets/widget", type = WidgetDescriptor[].class, componentType = WidgetDescriptor.class)
106    WidgetDescriptor[] subWidgets = new WidgetDescriptor[0];
107
108    /**
109     * @since 5.6
110     */
111    @XNodeList(value = "subWidgetRefs/widget", type = WidgetReferenceDescriptor[].class, componentType = WidgetReferenceDescriptor.class)
112    WidgetReferenceDescriptor[] subWidgetRefs = new WidgetReferenceDescriptor[0];
113
114    // set in method to mix single and multiple options
115    WidgetSelectOption[] selectOptions = new WidgetSelectOption[0];
116
117    @XNodeMap(value = "renderingInfos", key = "@mode", type = HashMap.class, componentType = RenderingInfosDescriptor.class)
118    Map<String, RenderingInfosDescriptor> renderingInfos = new HashMap<String, RenderingInfosDescriptor>();
119
120    @XNodeList(value = "categories/category", type = String[].class, componentType = String.class)
121    String[] categories = new String[0];
122
123    /**
124     * @since 6.0
125     */
126    @XNodeList(value = "aliases/alias", type = ArrayList.class, componentType = String.class)
127    List<String> aliases;
128
129    public String getName() {
130        return name;
131    }
132
133    public String getType() {
134        return type;
135    }
136
137    public FieldDefinition[] getFieldDefinitions() {
138        if (fields == null) {
139            return null;
140        }
141        FieldDefinition[] res = new FieldDefinition[fields.length];
142        for (int i = 0; i < fields.length; i++) {
143            res[i] = fields[i].getFieldDefinition();
144        }
145        return res;
146    }
147
148    public String getMode(String layoutMode) {
149        String mode = modes.get(layoutMode);
150        if (mode == null) {
151            mode = modes.get(BuiltinModes.ANY);
152        }
153        return mode;
154    }
155
156    public Map<String, String> getModes() {
157        return modes;
158    }
159
160    public String getRequired(String layoutMode, String mode) {
161        String res = "false";
162        Map<String, Serializable> props = getProperties(layoutMode, mode);
163        if (props != null && props.containsKey(WidgetDefinition.REQUIRED_PROPERTY_NAME)) {
164            Object value = props.get(WidgetDefinition.REQUIRED_PROPERTY_NAME);
165            if (value instanceof String) {
166                res = (String) value;
167            } else {
168                log.error(String.format("Invalid property \"%s\" on widget %s: %s",
169                        WidgetDefinition.REQUIRED_PROPERTY_NAME, value, name));
170            }
171        }
172        return res;
173    }
174
175    public String getLabel(String mode) {
176        String label = labels.get(mode);
177        if (label == null) {
178            label = labels.get(BuiltinModes.ANY);
179        }
180        return label;
181    }
182
183    public Map<String, String> getLabels() {
184        return labels;
185    }
186
187    public String getHelpLabel(String mode) {
188        String label = helpLabels.get(mode);
189        if (label == null) {
190            label = helpLabels.get(BuiltinModes.ANY);
191        }
192        return label;
193    }
194
195    public Map<String, String> getHelpLabels() {
196        return helpLabels;
197    }
198
199    public boolean isTranslated() {
200        return translated;
201    }
202
203    public Map<String, Serializable> getProperties(String layoutMode, String mode) {
204        Map<String, Serializable> modeProps = getProperties(properties, layoutMode);
205        Map<String, Serializable> widgetModeProps = getProperties(widgetModeProperties, mode);
206        if (modeProps == null && widgetModeProps == null) {
207            return null;
208        } else if (widgetModeProps == null) {
209            return modeProps;
210        } else if (modeProps == null) {
211            return widgetModeProps;
212        } else {
213            // take mode values, and override with widget mode values
214            Map<String, Serializable> res = new HashMap<String, Serializable>(modeProps);
215            res.putAll(widgetModeProps);
216            return res;
217        }
218    }
219
220    public Map<String, Map<String, Serializable>> getProperties() {
221        return getProperties(properties);
222    }
223
224    public Map<String, Map<String, Serializable>> getWidgetModeProperties() {
225        return getProperties(widgetModeProperties);
226    }
227
228    /**
229     * @since 5.7
230     * @see WidgetDefinition#getControls()
231     */
232    public Map<String, Map<String, Serializable>> getControls() {
233        if (controls == null) {
234            return null;
235        }
236        Map<String, Map<String, Serializable>> res = new HashMap<String, Map<String, Serializable>>();
237        for (Map.Entry<String, ControlsDescriptor> item : controls.entrySet()) {
238            Map<String, Serializable> props = new HashMap<String, Serializable>();
239            props.putAll(item.getValue().getControls());
240            res.put(item.getKey(), props);
241        }
242        return res;
243    }
244
245    public WidgetDefinition[] getSubWidgetDefinitions() {
246        WidgetDefinition[] csubWidgets = null;
247        if (subWidgets != null) {
248            csubWidgets = new WidgetDefinition[subWidgets.length];
249            for (int i = 0; i < subWidgets.length; i++) {
250                csubWidgets[i] = subWidgets[i].getWidgetDefinition();
251            }
252        }
253        return csubWidgets;
254    }
255
256    public WidgetReference[] getSubWidgetReferences() {
257        WidgetReference[] csubWidgets = null;
258        if (subWidgetRefs != null) {
259            csubWidgets = new WidgetReference[subWidgetRefs.length];
260            for (int i = 0; i < subWidgetRefs.length; i++) {
261                csubWidgets[i] = new WidgetReferenceImpl(subWidgetRefs[i].getCategory(), subWidgetRefs[i].getName());
262            }
263        }
264        return csubWidgets;
265    }
266
267    public static Map<String, Serializable> getProperties(Map<String, PropertiesDescriptor> map, String mode) {
268        if (map == null) {
269            return null;
270        }
271        PropertiesDescriptor defaultProps = map.get(BuiltinModes.ANY);
272        PropertiesDescriptor props = map.get(mode);
273
274        if (defaultProps == null && props == null) {
275            return null;
276        } else if (defaultProps == null) {
277            return props.getProperties();
278        } else if (props == null) {
279            return defaultProps.getProperties();
280        } else {
281            // take any mode values, and override with given mode values
282            Map<String, Serializable> res = new HashMap<String, Serializable>(defaultProps.getProperties());
283            res.putAll(props.getProperties());
284            return res;
285        }
286    }
287
288    public static Map<String, Map<String, Serializable>> getProperties(Map<String, PropertiesDescriptor> map) {
289        if (map == null) {
290            return null;
291        }
292        Map<String, Map<String, Serializable>> res = new HashMap<String, Map<String, Serializable>>();
293        for (Map.Entry<String, PropertiesDescriptor> item : map.entrySet()) {
294            Map<String, Serializable> props = new HashMap<String, Serializable>();
295            props.putAll(item.getValue().getProperties());
296            res.put(item.getKey(), props);
297        }
298        return res;
299    }
300
301    public WidgetSelectOption[] getSelectOptions() {
302        return selectOptions;
303    }
304
305    @XContent("selectOptions")
306    public void setSelectOptions(DocumentFragment selectOptionsDOM) {
307        XMap xmap = new XMap();
308        xmap.register(WidgetSelectOptionDescriptor.class);
309        xmap.register(WidgetSelectOptionsDescriptor.class);
310        Node p = selectOptionsDOM.getFirstChild();
311        List<WidgetSelectOption> options = new ArrayList<WidgetSelectOption>();
312        while (p != null) {
313            if (p.getNodeType() == Node.ELEMENT_NODE) {
314                Object desc = xmap.load((Element) p);
315                if (desc instanceof WidgetSelectOptionDescriptor) {
316                    options.add(((WidgetSelectOptionDescriptor) desc).getWidgetSelectOption());
317                } else if (desc instanceof WidgetSelectOptionsDescriptor) {
318                    options.add(((WidgetSelectOptionsDescriptor) desc).getWidgetSelectOption());
319                } else {
320                    log.error("Unknown resolution of select option");
321                }
322            }
323            p = p.getNextSibling();
324        }
325        selectOptions = options.toArray(new WidgetSelectOption[0]);
326    }
327
328    /**
329     * Returns the categories for this widget type, so that it can be stored in the corresponding registry.
330     *
331     * @since 5.5
332     */
333    public String[] getCategories() {
334        return categories;
335    }
336
337    /**
338     * @since 6.0
339     */
340    public List<String> getAliases() {
341        return aliases;
342    }
343
344    public WidgetDefinition getWidgetDefinition() {
345        Map<String, String> clabels = null;
346        if (labels != null) {
347            clabels = new HashMap<String, String>();
348            clabels.putAll(labels);
349        }
350        Map<String, String> chelpLabels = null;
351        if (helpLabels != null) {
352            chelpLabels = new HashMap<String, String>();
353            chelpLabels.putAll(helpLabels);
354        }
355        Map<String, String> cmodes = null;
356        if (modes != null) {
357            cmodes = new HashMap<String, String>();
358            cmodes.putAll(modes);
359        }
360        FieldDefinition[] cfieldDefinitions = getFieldDefinitions();
361        WidgetDefinition[] csubWidgets = getSubWidgetDefinitions();
362        WidgetReference[] csubwidgetRefs = getSubWidgetReferences();
363        WidgetSelectOption[] cselectOptions = null;
364        if (selectOptions != null) {
365            cselectOptions = new WidgetSelectOption[selectOptions.length];
366            for (int i = 0; i < selectOptions.length; i++) {
367                cselectOptions[i] = selectOptions[i].clone();
368            }
369        }
370        Map<String, List<RenderingInfo>> crenderingInfos = null;
371        if (renderingInfos != null) {
372            crenderingInfos = new HashMap<String, List<RenderingInfo>>();
373            for (Map.Entry<String, RenderingInfosDescriptor> item : renderingInfos.entrySet()) {
374                RenderingInfosDescriptor infos = item.getValue();
375                List<RenderingInfo> clonedInfos = null;
376                if (infos != null) {
377                    clonedInfos = new ArrayList<RenderingInfo>();
378                    for (RenderingInfoDescriptor info : infos.getRenderingInfos()) {
379                        clonedInfos.add(info.getRenderingInfo());
380                    }
381                }
382                crenderingInfos.put(item.getKey(), clonedInfos);
383            }
384        }
385        WidgetDefinitionImpl clone = new WidgetDefinitionImpl(name, type, clabels, chelpLabels, translated, cmodes,
386                cfieldDefinitions, getProperties(), getWidgetModeProperties(), csubWidgets, cselectOptions);
387        clone.setRenderingInfos(crenderingInfos);
388        clone.setSubWidgetReferences(csubwidgetRefs);
389        clone.setHandlingLabels(handlingLabels);
390        clone.setControls(getControls());
391        clone.setTypeCategory(typeCategory);
392        if (aliases != null) {
393            clone.setAliases(new ArrayList<String>(aliases));
394        }
395        return clone;
396    }
397}