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