001/*
002 * (C) Copyright 2010 Nuxeo SA (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 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.forms.layout.io;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.io.Serializable;
022import java.io.UnsupportedEncodingException;
023import java.net.URLDecoder;
024import java.net.URLEncoder;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032import net.sf.json.JSONArray;
033import net.sf.json.JSONObject;
034
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
039import org.nuxeo.ecm.platform.forms.layout.api.Layout;
040import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
041import org.nuxeo.ecm.platform.forms.layout.api.LayoutRow;
042import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
043import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeConfiguration;
044import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeDefinition;
045import org.nuxeo.ecm.platform.forms.layout.api.RenderingInfo;
046import org.nuxeo.ecm.platform.forms.layout.api.Widget;
047import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
048import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
049import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOption;
050import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOptions;
051import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
052import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
053import org.nuxeo.ecm.platform.forms.layout.api.converters.LayoutConversionContext;
054import org.nuxeo.ecm.platform.forms.layout.api.converters.WidgetDefinitionConverter;
055import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl;
056import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutDefinitionImpl;
057import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowDefinitionImpl;
058import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeConfigurationImpl;
059import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeDefinitionComparator;
060import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeDefinitionImpl;
061import org.nuxeo.ecm.platform.forms.layout.api.impl.RenderingInfoImpl;
062import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
063import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
064import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionImpl;
065import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionsImpl;
066import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeConfigurationImpl;
067import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeDefinitionComparator;
068import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeDefinitionImpl;
069import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
070import org.nuxeo.runtime.api.Framework;
071
072/**
073 * JSON exporter for layout objects
074 *
075 * @author Anahide Tchertchian
076 * @since 5.4.2
077 */
078public class JSONLayoutExporter {
079
080    private static final Log log = LogFactory.getLog(JSONLayoutExporter.class);
081
082    public static final String ENCODED_VALUES_ENCODING = "UTF-8";
083
084    public static String encode(JSONObject jsonObject) throws UnsupportedEncodingException {
085        String json = jsonObject.toString();
086        String encodedValues = Base64.encodeBytes(json.getBytes(), Base64.GZIP | Base64.DONT_BREAK_LINES);
087        return URLEncoder.encode(encodedValues, ENCODED_VALUES_ENCODING);
088    }
089
090    public static JSONObject decode(String json) throws UnsupportedEncodingException {
091        String decodedValues = URLDecoder.decode(json, ENCODED_VALUES_ENCODING);
092        json = new String(Base64.decode(decodedValues));
093        return JSONObject.fromObject(json);
094    }
095
096    /**
097     * @since 5.5
098     * @throws IOException
099     */
100    public static void export(String category, LayoutDefinition layoutDef, LayoutConversionContext ctx,
101            List<WidgetDefinitionConverter> widgetConverters, OutputStream out) throws IOException {
102        JSONObject res = exportToJson(category, layoutDef, ctx, widgetConverters);
103        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
104    }
105
106    public static void export(WidgetTypeDefinition def, OutputStream out) throws IOException {
107        JSONObject res = exportToJson(def);
108        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
109    }
110
111    public static void export(List<WidgetTypeDefinition> defs, OutputStream out) throws IOException {
112        JSONObject res = new JSONObject();
113        if (defs != null) {
114            // sort so that order is deterministic
115            Collections.sort(defs, new WidgetTypeDefinitionComparator(false));
116        }
117        for (WidgetTypeDefinition def : defs) {
118            res.element(def.getName(), exportToJson(def));
119        }
120        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
121    }
122
123    /**
124     * @since 6.0
125     */
126    public static void exportLayoutType(LayoutTypeDefinition def, OutputStream out) throws IOException {
127        JSONObject res = exportToJson(def);
128        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
129    }
130
131    /**
132     * @since 6.0
133     */
134    public static void exportLayoutTypes(List<LayoutTypeDefinition> defs, OutputStream out) throws IOException {
135        JSONObject res = new JSONObject();
136        if (defs != null) {
137            // sort so that order is deterministic
138            Collections.sort(defs, new LayoutTypeDefinitionComparator());
139        }
140        for (LayoutTypeDefinition def : defs) {
141            res.element(def.getName(), exportToJson(def));
142        }
143        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
144    }
145
146    public static JSONObject exportToJson(WidgetTypeDefinition def) {
147        JSONObject json = new JSONObject();
148        json.element("name", def.getName());
149        List<String> caliases = def.getAliases();
150        if (caliases != null && !caliases.isEmpty()) {
151            JSONArray aliases = new JSONArray();
152            for (String alias : caliases) {
153                aliases.add(alias);
154            }
155            json.element("aliases", aliases);
156        }
157        json.element("handlerClassName", def.getHandlerClassName());
158        JSONObject props = exportStringPropsToJson(def.getProperties());
159        if (!props.isEmpty()) {
160            json.element("properties", props);
161        }
162        WidgetTypeConfiguration conf = def.getConfiguration();
163        if (conf != null) {
164            json.element("configuration", exportToJson(conf));
165        }
166        return json;
167    }
168
169    @SuppressWarnings("unchecked")
170    public static WidgetTypeDefinition importWidgetTypeDefinition(JSONObject jsonDef) {
171        String name = jsonDef.optString("name");
172        String handlerClass = jsonDef.optString("handlerClassName");
173        Map<String, String> properties = importStringProps(jsonDef.optJSONObject("properties"));
174        WidgetTypeConfiguration conf = importWidgetTypeConfiguration(jsonDef.optJSONObject("configuration"));
175        List<String> aliases = new ArrayList<String>();
176        JSONArray jaliases = jsonDef.optJSONArray("aliases");
177        if (jaliases != null) {
178            aliases.addAll(jaliases);
179        }
180        WidgetTypeDefinitionImpl res = new WidgetTypeDefinitionImpl(name, handlerClass, properties, conf);
181        res.setAliases(aliases);
182        return res;
183    }
184
185    public static JSONObject exportToJson(WidgetTypeConfiguration conf) {
186        JSONObject json = new JSONObject();
187        json.element("title", conf.getTitle());
188        json.element("description", conf.getDescription());
189        String demoId = conf.getDemoId();
190        if (demoId != null) {
191            JSONObject demoInfo = new JSONObject();
192            demoInfo.element("id", demoId);
193            demoInfo.element("previewEnabled", conf.isDemoPreviewEnabled());
194            json.element("demo", demoInfo);
195        }
196        json.element("sinceVersion", conf.getSinceVersion());
197        String deprVersion = conf.getDeprecatedVersion();
198        if (!StringUtils.isBlank(deprVersion)) {
199            json.element("deprecatedVersion", deprVersion);
200        }
201        JSONObject confProps = exportPropsToJson(conf.getConfProperties());
202        if (!confProps.isEmpty()) {
203            json.element("confProperties", confProps);
204        }
205
206        JSONArray supportedModes = new JSONArray();
207        List<String> confSupportedModes = conf.getSupportedModes();
208        if (confSupportedModes != null) {
209            supportedModes.addAll(confSupportedModes);
210        }
211        if (!supportedModes.isEmpty()) {
212            json.element("supportedModes", supportedModes);
213        }
214
215        if (conf.isAcceptingSubWidgets()) {
216            json.element("acceptingSubWidgets", conf.isAcceptingSubWidgets());
217        }
218        if (conf.isHandlingLabels()) {
219            json.element("handlingLabels", conf.isHandlingLabels());
220        }
221        JSONArray supportedControls = new JSONArray();
222        List<String> confSupportedControls = conf.getSupportedControls();
223        if (confSupportedControls != null) {
224            supportedControls.addAll(confSupportedControls);
225        }
226        if (!supportedControls.isEmpty()) {
227            json.element("supportedControls", supportedControls);
228        }
229        if (conf.isContainingForm()) {
230            json.element("containingForm", true);
231        }
232
233        JSONObject fields = new JSONObject();
234        fields.element("list", conf.isList());
235        fields.element("complex", conf.isComplex());
236
237        JSONArray supportedTypes = new JSONArray();
238        List<String> confSupportedTypes = conf.getSupportedFieldTypes();
239        if (confSupportedTypes != null) {
240            supportedTypes.addAll(confSupportedTypes);
241        }
242        if (!supportedTypes.isEmpty()) {
243            fields.element("supportedTypes", supportedTypes);
244        }
245
246        JSONArray defaultTypes = new JSONArray();
247        List<String> confDefaultTypes = conf.getDefaultFieldTypes();
248        if (confDefaultTypes != null) {
249            defaultTypes.addAll(confDefaultTypes);
250        }
251        if (!defaultTypes.isEmpty()) {
252            fields.element("defaultTypes", defaultTypes);
253        }
254
255        JSONArray defaultFieldDefs = new JSONArray();
256        List<FieldDefinition> fieldDefs = conf.getDefaultFieldDefinitions();
257        if (fieldDefs != null) {
258            for (FieldDefinition fieldDef : fieldDefs) {
259                defaultFieldDefs.add(exportToJson(fieldDef));
260            }
261        }
262        if (!defaultFieldDefs.isEmpty()) {
263            fields.element("defaultConfiguration", defaultFieldDefs);
264        }
265
266        Map<String, List<LayoutDefinition>> fieldLayouts = conf.getFieldLayouts();
267        if (fieldLayouts != null) {
268            List<String> modes = new ArrayList<String>(fieldLayouts.keySet());
269            // sort so that order is deterministic
270            Collections.sort(modes);
271            JSONObject layouts = new JSONObject();
272            for (String mode : modes) {
273                JSONArray modeLayouts = new JSONArray();
274                for (LayoutDefinition layoutDef : fieldLayouts.get(mode)) {
275                    modeLayouts.add(exportToJson(null, layoutDef));
276                }
277                layouts.element(mode, modeLayouts);
278            }
279            if (!layouts.isEmpty()) {
280                fields.element("layouts", layouts);
281            }
282        }
283
284        json.element("fields", fields);
285
286        JSONArray cats = new JSONArray();
287        List<String> confCats = conf.getCategories();
288        if (confCats != null) {
289            cats.addAll(confCats);
290        }
291        if (!cats.isEmpty()) {
292            json.element("categories", cats);
293        }
294
295        JSONObject props = new JSONObject();
296        Map<String, List<LayoutDefinition>> confLayouts = conf.getPropertyLayouts();
297        if (confLayouts != null) {
298            List<String> modes = new ArrayList<String>(confLayouts.keySet());
299            // sort so that order is deterministic
300            Collections.sort(modes);
301            JSONObject layouts = new JSONObject();
302            for (String mode : modes) {
303                JSONArray modeLayouts = new JSONArray();
304                for (LayoutDefinition layoutDef : confLayouts.get(mode)) {
305                    modeLayouts.add(exportToJson(null, layoutDef));
306                }
307                layouts.element(mode, modeLayouts);
308            }
309            if (!layouts.isEmpty()) {
310                props.element("layouts", layouts);
311            }
312        }
313
314        Map<String, Map<String, Serializable>> defaultPropValues = conf.getDefaultPropertyValues();
315        if (defaultPropValues != null && !defaultPropValues.isEmpty()) {
316            json.element("defaultPropertyValues", exportPropsByModeToJson(defaultPropValues));
317        }
318
319        if (!props.isEmpty()) {
320            json.element("properties", props);
321        }
322
323        Map<String, Map<String, Serializable>> defaultControlValues = conf.getDefaultControlValues();
324        if (defaultControlValues != null && !defaultControlValues.isEmpty()) {
325            json.element("defaultControlValues", exportPropsByModeToJson(defaultControlValues));
326        }
327
328        return json;
329    }
330
331    @SuppressWarnings("unchecked")
332    public static WidgetTypeConfiguration importWidgetTypeConfiguration(JSONObject conf) {
333        WidgetTypeConfigurationImpl res = new WidgetTypeConfigurationImpl();
334        if (conf == null) {
335            return res;
336        }
337        res.setTitle(conf.getString("title"));
338        res.setDescription(conf.optString("description"));
339        res.setSinceVersion(conf.optString("sinceVersion"));
340        res.setDeprecatedVersion(conf.optString("deprecatedVersion"));
341
342        JSONObject demoInfo = conf.optJSONObject("demo");
343        String demoId = null;
344        boolean demoPreviewEnabled = false;
345        if (demoInfo != null && !demoInfo.isNullObject()) {
346            demoId = demoInfo.optString("id");
347            demoPreviewEnabled = demoInfo.optBoolean("previewEnabled");
348        }
349        res.setDemoId(demoId);
350        res.setDemoPreviewEnabled(demoPreviewEnabled);
351
352        res.setProperties(importProps(conf.optJSONObject("confProperties")));
353
354        List<String> confSupportedModes = new ArrayList<String>();
355        JSONArray supportedModes = conf.optJSONArray("supportedModes");
356        if (supportedModes != null) {
357            confSupportedModes.addAll(supportedModes);
358        }
359        res.setSupportedModes(confSupportedModes);
360
361        res.setAcceptingSubWidgets(conf.optBoolean("acceptingSubWidgets", false));
362        res.setHandlingLabels(conf.optBoolean("handlingLabels", false));
363        List<String> confSupportedControls = new ArrayList<String>();
364        JSONArray supportedControls = conf.optJSONArray("supportedControls");
365        if (supportedControls != null) {
366            confSupportedControls.addAll(supportedControls);
367        }
368        res.setSupportedControls(confSupportedControls);
369        res.setContainingForm(conf.optBoolean("containingForm", false));
370
371        JSONObject fields = conf.optJSONObject("fields");
372        boolean list = false;
373        boolean complex = false;
374        List<String> confSupportedTypes = new ArrayList<String>();
375        List<String> confDefaultTypes = new ArrayList<String>();
376        List<FieldDefinition> defaultFieldDefinitions = new ArrayList<FieldDefinition>();
377        Map<String, List<LayoutDefinition>> fieldLayouts = new HashMap<String, List<LayoutDefinition>>();
378        if (fields != null && !fields.isNullObject()) {
379            list = fields.optBoolean("list", false);
380            complex = fields.optBoolean("complex", false);
381            JSONArray supportedTypes = fields.optJSONArray("supportedTypes");
382            if (supportedTypes != null) {
383                confSupportedTypes.addAll(supportedTypes);
384            }
385            JSONArray defaultTypes = fields.optJSONArray("defaultTypes");
386            if (defaultTypes != null) {
387                confDefaultTypes.addAll(defaultTypes);
388            }
389            JSONArray jfields = fields.optJSONArray("defaultConfiguration");
390            if (jfields != null) {
391                for (Object item : jfields) {
392                    defaultFieldDefinitions.add(importFieldDefinition((JSONObject) item));
393                }
394            }
395            JSONObject layouts = fields.optJSONObject("layouts");
396            if (layouts != null && !layouts.isNullObject()) {
397                for (Object item : layouts.keySet()) {
398                    String mode = (String) item;
399                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
400                    JSONArray modeLayouts = layouts.getJSONArray(mode);
401                    if (modeLayouts != null && !mode.isEmpty()) {
402                        for (Object subitem : modeLayouts) {
403                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
404                        }
405                    }
406                    fieldLayouts.put(mode, layoutDefs);
407                }
408            }
409        }
410        res.setList(list);
411        res.setComplex(complex);
412        res.setSupportedFieldTypes(confSupportedTypes);
413        res.setDefaultFieldTypes(confDefaultTypes);
414        res.setDefaultFieldDefinitions(defaultFieldDefinitions);
415        res.setFieldLayouts(fieldLayouts);
416
417        JSONArray cats = conf.optJSONArray("categories");
418        List<String> confCats = new ArrayList<String>();
419        if (cats != null) {
420            confCats.addAll(cats);
421        }
422        res.setCategories(confCats);
423
424        JSONObject props = conf.optJSONObject("properties");
425        Map<String, List<LayoutDefinition>> confLayouts = new HashMap<String, List<LayoutDefinition>>();
426        if (props != null && !props.isNullObject()) {
427            JSONObject layouts = props.optJSONObject("layouts");
428            if (layouts != null && !layouts.isNullObject()) {
429                for (Object item : layouts.keySet()) {
430                    String mode = (String) item;
431                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
432                    JSONArray modeLayouts = layouts.getJSONArray(mode);
433                    if (modeLayouts != null && !mode.isEmpty()) {
434                        for (Object subitem : modeLayouts) {
435                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
436                        }
437                    }
438                    confLayouts.put(mode, layoutDefs);
439                }
440            }
441        }
442
443        res.setPropertyLayouts(confLayouts);
444
445        JSONObject defaultPropertyValues = conf.optJSONObject("defaultPropertyValues");
446        Map<String, Map<String, Serializable>> confDefaultProps = importPropsByMode(defaultPropertyValues);
447        res.setDefaultPropertyValues(confDefaultProps);
448
449        JSONObject defaultControlValues = conf.optJSONObject("defaultControlValues");
450        Map<String, Map<String, Serializable>> confDefaultControls = importPropsByMode(defaultControlValues);
451        res.setDefaultControlValues(confDefaultControls);
452
453        return res;
454    }
455
456    /**
457     * @since 6.0
458     */
459    public static JSONObject exportToJson(LayoutTypeDefinition def) {
460        JSONObject json = new JSONObject();
461        json.element("name", def.getName());
462
463        List<String> caliases = def.getAliases();
464        if (caliases != null && !caliases.isEmpty()) {
465            JSONArray aliases = new JSONArray();
466            for (String alias : caliases) {
467                aliases.add(alias);
468            }
469            json.element("aliases", aliases);
470        }
471
472        JSONObject templates = exportStringPropsToJson(def.getTemplates());
473        if (!templates.isEmpty()) {
474            json.element("templates", templates);
475        }
476
477        LayoutTypeConfiguration conf = def.getConfiguration();
478        if (conf != null) {
479            json.element("configuration", exportToJson(conf));
480        }
481        return json;
482    }
483
484    /**
485     * @since 6.0
486     */
487    @SuppressWarnings("unchecked")
488    public static LayoutTypeDefinition importLayoutTypeDefinition(JSONObject jsonDef) {
489        String name = jsonDef.optString("name");
490        Map<String, String> templates = importStringProps(jsonDef.optJSONObject("templates"));
491        LayoutTypeConfiguration conf = importLayoutTypeConfiguration(jsonDef.optJSONObject("configuration"));
492        List<String> aliases = new ArrayList<String>();
493        JSONArray jaliases = jsonDef.optJSONArray("aliases");
494        if (jaliases != null) {
495            aliases.addAll(jaliases);
496        }
497        LayoutTypeDefinitionImpl res = new LayoutTypeDefinitionImpl(name, templates, conf);
498        res.setAliases(aliases);
499        return res;
500    }
501
502    /**
503     * @since 6.0
504     */
505    public static JSONObject exportToJson(LayoutTypeConfiguration conf) {
506        JSONObject json = new JSONObject();
507        json.element("title", conf.getTitle());
508        json.element("description", conf.getDescription());
509        String demoId = conf.getDemoId();
510        if (demoId != null) {
511            JSONObject demoInfo = new JSONObject();
512            demoInfo.element("id", demoId);
513            demoInfo.element("previewEnabled", conf.isDemoPreviewEnabled());
514            json.element("demo", demoInfo);
515        }
516        json.element("sinceVersion", conf.getSinceVersion());
517        String deprVersion = conf.getDeprecatedVersion();
518        if (!StringUtils.isBlank(deprVersion)) {
519            json.element("deprecatedVersion", deprVersion);
520        }
521
522        JSONArray supportedModes = new JSONArray();
523        List<String> confSupportedModes = conf.getSupportedModes();
524        if (confSupportedModes != null) {
525            supportedModes.addAll(confSupportedModes);
526        }
527        if (!supportedModes.isEmpty()) {
528            json.element("supportedModes", supportedModes);
529        }
530
531        if (conf.isHandlingLabels()) {
532            json.element("handlingLabels", conf.isHandlingLabels());
533        }
534        JSONArray supportedControls = new JSONArray();
535        List<String> confSupportedControls = conf.getSupportedControls();
536        if (confSupportedControls != null) {
537            supportedControls.addAll(confSupportedControls);
538        }
539        if (!supportedControls.isEmpty()) {
540            json.element("supportedControls", supportedControls);
541        }
542        if (conf.isContainingForm()) {
543            json.element("containingForm", true);
544        }
545
546        JSONArray cats = new JSONArray();
547        List<String> confCats = conf.getCategories();
548        if (confCats != null) {
549            cats.addAll(confCats);
550        }
551        if (!cats.isEmpty()) {
552            json.element("categories", cats);
553        }
554
555        JSONObject props = new JSONObject();
556        Map<String, List<LayoutDefinition>> confLayouts = conf.getPropertyLayouts();
557        if (confLayouts != null) {
558            List<String> modes = new ArrayList<String>(confLayouts.keySet());
559            // sort so that order is deterministic
560            Collections.sort(modes);
561            JSONObject layouts = new JSONObject();
562            for (String mode : modes) {
563                JSONArray modeLayouts = new JSONArray();
564                for (LayoutDefinition layoutDef : confLayouts.get(mode)) {
565                    modeLayouts.add(exportToJson(null, layoutDef));
566                }
567                layouts.element(mode, modeLayouts);
568            }
569            if (!layouts.isEmpty()) {
570                props.element("layouts", layouts);
571            }
572        }
573
574        Map<String, Map<String, Serializable>> defaultPropValues = conf.getDefaultPropertyValues();
575        if (defaultPropValues != null && !defaultPropValues.isEmpty()) {
576            json.element("defaultPropertyValues", exportPropsByModeToJson(defaultPropValues));
577        }
578
579        if (!props.isEmpty()) {
580            json.element("properties", props);
581        }
582
583        return json;
584
585    }
586
587    /**
588     * @since 6.0
589     */
590    @SuppressWarnings("unchecked")
591    public static LayoutTypeConfiguration importLayoutTypeConfiguration(JSONObject conf) {
592        LayoutTypeConfigurationImpl res = new LayoutTypeConfigurationImpl();
593        if (conf == null) {
594            return res;
595        }
596        res.setTitle(conf.getString("title"));
597        res.setDescription(conf.optString("description"));
598        res.setSinceVersion(conf.optString("sinceVersion"));
599        res.setDeprecatedVersion(conf.optString("deprecatedVersion"));
600
601        JSONObject demoInfo = conf.optJSONObject("demo");
602        String demoId = null;
603        boolean demoPreviewEnabled = false;
604        if (demoInfo != null && !demoInfo.isNullObject()) {
605            demoId = demoInfo.optString("id");
606            demoPreviewEnabled = demoInfo.optBoolean("previewEnabled");
607        }
608        res.setDemoId(demoId);
609        res.setDemoPreviewEnabled(demoPreviewEnabled);
610
611        List<String> confSupportedModes = new ArrayList<String>();
612        JSONArray supportedModes = conf.optJSONArray("supportedModes");
613        if (supportedModes != null) {
614            confSupportedModes.addAll(supportedModes);
615        }
616        res.setSupportedModes(confSupportedModes);
617
618        res.setHandlingLabels(conf.optBoolean("handlingLabels", false));
619        List<String> confSupportedControls = new ArrayList<String>();
620        JSONArray supportedControls = conf.optJSONArray("supportedControls");
621        if (supportedControls != null) {
622            confSupportedControls.addAll(supportedControls);
623        }
624        res.setSupportedControls(confSupportedControls);
625        res.setContainingForm(conf.optBoolean("containingForm", false));
626
627        JSONArray cats = conf.optJSONArray("categories");
628        List<String> confCats = new ArrayList<String>();
629        if (cats != null) {
630            confCats.addAll(cats);
631        }
632        res.setCategories(confCats);
633
634        JSONObject props = conf.optJSONObject("properties");
635        Map<String, List<LayoutDefinition>> confLayouts = new HashMap<String, List<LayoutDefinition>>();
636        if (props != null && !props.isNullObject()) {
637            JSONObject layouts = props.optJSONObject("layouts");
638            if (layouts != null && !layouts.isNullObject()) {
639                for (Object item : layouts.keySet()) {
640                    String mode = (String) item;
641                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
642                    JSONArray modeLayouts = layouts.getJSONArray(mode);
643                    if (modeLayouts != null && !mode.isEmpty()) {
644                        for (Object subitem : modeLayouts) {
645                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
646                        }
647                    }
648                    confLayouts.put(mode, layoutDefs);
649                }
650            }
651        }
652
653        res.setPropertyLayouts(confLayouts);
654
655        JSONObject defaultPropertyValues = conf.optJSONObject("defaultPropertyValues");
656        Map<String, Map<String, Serializable>> confDefaultProps = importPropsByMode(defaultPropertyValues);
657        res.setDefaultPropertyValues(confDefaultProps);
658
659        return res;
660    }
661
662    public static JSONObject exportToJson(String category, LayoutDefinition layoutDef) {
663        return exportToJson(category, layoutDef, null, null);
664    }
665
666    /**
667     * Returns the JSON export of this layout definition
668     *
669     * @since 5.5
670     * @param category the category of the layout, needed for retrieval of referenced global widgets.
671     * @param layoutDef the layout definition
672     * @param ctx the widget conversion context
673     * @param widgetConverters the list of ordered widget converters to use before export
674     */
675    public static JSONObject exportToJson(String category, LayoutDefinition layoutDef, LayoutConversionContext ctx,
676            List<WidgetDefinitionConverter> widgetConverters) {
677        JSONObject json = new JSONObject();
678        json.element("name", layoutDef.getName());
679
680        String type = layoutDef.getType();
681        if (type != null) {
682            json.element("type", type);
683        }
684
685        String typeCat = layoutDef.getTypeCategory();
686        if (typeCat != null) {
687            json.element("typeCategory", typeCat);
688        }
689
690        JSONObject templates = exportStringPropsToJson(layoutDef.getTemplates());
691        if (!templates.isEmpty()) {
692            json.element("templates", templates);
693        }
694
695        JSONObject props = exportPropsByModeToJson(layoutDef.getProperties());
696        if (!props.isEmpty()) {
697            json.element("properties", props);
698        }
699
700        JSONArray rows = new JSONArray();
701        LayoutRowDefinition[] defRows = layoutDef.getRows();
702        List<WidgetReference> widgetsToExport = new ArrayList<WidgetReference>();
703        if (defRows != null) {
704            int rowIndex = -1;
705            for (LayoutRowDefinition layoutRowDef : defRows) {
706                rowIndex++;
707                rows.add(exportToJson(layoutRowDef, layoutRowDef.getDefaultName(rowIndex)));
708                WidgetReference[] widgets = layoutRowDef.getWidgetReferences();
709                if (widgets != null) {
710                    for (WidgetReference widget : widgets) {
711                        widgetsToExport.add(widget);
712                    }
713                }
714            }
715        }
716
717        if (!rows.isEmpty()) {
718            json.element("rows", rows);
719        }
720        LayoutStore webLayoutManager = Framework.getLocalService(LayoutStore.class);
721        JSONArray widgets = new JSONArray();
722        for (WidgetReference widgetRef : widgetsToExport) {
723            WidgetDefinition widgetDef = exportWidgetReference(widgetRef, category, layoutDef, ctx, webLayoutManager,
724                    widgetConverters);
725            if (widgetDef != null) {
726                widgets.add(exportToJson(widgetDef, ctx, widgetConverters));
727
728                // also export local subwidgets references
729                WidgetReference[] subwidgetRefs = widgetDef.getSubWidgetReferences();
730                if (subwidgetRefs != null) {
731                    for (WidgetReference subwidgetRef : subwidgetRefs) {
732                        WidgetDefinition subwidgetDef = exportWidgetReference(subwidgetRef, category, layoutDef, ctx,
733                                webLayoutManager, widgetConverters);
734                        if (subwidgetDef != null) {
735                            widgets.add(exportToJson(subwidgetDef, ctx, widgetConverters));
736                        }
737                    }
738                }
739            }
740        }
741        if (!widgets.isEmpty()) {
742            json.element("widgets", widgets);
743        }
744
745        JSONObject renderingInfos = exportRenderingInfosByModeToJson(layoutDef.getRenderingInfos());
746        if (!renderingInfos.isEmpty()) {
747            json.element("renderingInfos", renderingInfos);
748        }
749
750        List<String> caliases = layoutDef.getAliases();
751        if (caliases != null && !caliases.isEmpty()) {
752            JSONArray aliases = new JSONArray();
753            for (String alias : caliases) {
754                aliases.add(alias);
755            }
756            json.element("aliases", aliases);
757        }
758
759        return json;
760    }
761
762    protected static WidgetDefinition exportWidgetReference(WidgetReference widgetRef, String category,
763            LayoutDefinition layoutDef, LayoutConversionContext ctx, LayoutStore webLayoutManager,
764            List<WidgetDefinitionConverter> widgetConverters) {
765        String widgetName = widgetRef.getName();
766        WidgetDefinition widgetDef = layoutDef.getWidgetDefinition(widgetName);
767        if (widgetDef == null) {
768            String cat = widgetRef.getCategory();
769            if (cat == null) {
770                cat = category;
771            }
772            widgetDef = webLayoutManager.getWidgetDefinition(cat, widgetName);
773        }
774        if (widgetDef == null) {
775            log.error(String.format("No definition found for widget '%s' in layout '%s' " + "=> cannot export",
776                    widgetName, layoutDef.getName()));
777        } else {
778            if (widgetConverters != null) {
779                for (WidgetDefinitionConverter conv : widgetConverters) {
780                    widgetDef = conv.getWidgetDefinition(widgetDef, ctx);
781                }
782            }
783        }
784        return widgetDef;
785    }
786
787    @SuppressWarnings("unchecked")
788    public static LayoutDefinition importLayoutDefinition(JSONObject layoutDef) {
789        String name = layoutDef.optString("name", null);
790        String type = layoutDef.optString("type", null);
791        String typeCat = layoutDef.optString("typeCategory", null);
792        Map<String, String> templates = importStringProps(layoutDef.optJSONObject("templates"));
793        Map<String, Map<String, Serializable>> properties = importPropsByMode(layoutDef.optJSONObject("properties"));
794
795        List<LayoutRowDefinition> rows = new ArrayList<LayoutRowDefinition>();
796        JSONArray jrows = layoutDef.optJSONArray("rows");
797        if (jrows != null) {
798            for (Object item : jrows) {
799                rows.add(importLayoutRowDefinition((JSONObject) item));
800            }
801        }
802
803        List<WidgetDefinition> widgets = new ArrayList<WidgetDefinition>();
804        JSONArray jwidgets = layoutDef.optJSONArray("widgets");
805        if (jwidgets != null) {
806            for (Object item : jwidgets) {
807                widgets.add(importWidgetDefinition((JSONObject) item));
808            }
809        }
810
811        Map<String, List<RenderingInfo>> renderingInfos = importRenderingInfosByMode(layoutDef.optJSONObject("renderingInfos"));
812
813        List<String> aliases = new ArrayList<String>();
814        JSONArray jaliases = layoutDef.optJSONArray("aliases");
815        if (jaliases != null) {
816            aliases.addAll(jaliases);
817        }
818
819        LayoutDefinitionImpl res = new LayoutDefinitionImpl(name, properties, templates, rows, widgets);
820        res.setRenderingInfos(renderingInfos);
821        res.setType(type);
822        res.setTypeCategory(typeCat);
823        res.setAliases(aliases);
824        return res;
825    }
826
827    /**
828     * @since 6.0
829     */
830    public static JSONObject exportToJson(LayoutRowDefinition layoutRowDef, String defaultRowName) {
831        JSONObject json = new JSONObject();
832        String name = layoutRowDef.getName();
833        if (name != null) {
834            json.element("name", name);
835        } else if (defaultRowName != null) {
836            json.element("name", defaultRowName);
837        }
838        // fill selection info only if that's not the default value from the
839        // definition
840        if (layoutRowDef.isAlwaysSelected()) {
841            json.element("alwaysSelected", true);
842        }
843        if (!layoutRowDef.isSelectedByDefault()) {
844            json.element("selectedByDefault", false);
845        }
846        JSONObject props = exportPropsByModeToJson(layoutRowDef.getProperties());
847        if (!props.isEmpty()) {
848            json.element("properties", props);
849        }
850        JSONArray widgets = new JSONArray();
851        WidgetReference[] defWidgets = layoutRowDef.getWidgetReferences();
852        if (defWidgets != null) {
853            for (WidgetReference widget : defWidgets) {
854                widgets.add(exportToJson(widget));
855            }
856        }
857        if (!widgets.isEmpty()) {
858            json.element("widgets", widgets);
859        }
860        return json;
861    }
862
863    public static JSONObject exportToJson(LayoutRowDefinition layoutRowDef) {
864        return exportToJson(layoutRowDef, null);
865    }
866
867    public static LayoutRowDefinition importLayoutRowDefinition(JSONObject layoutRowDef) {
868        String name = layoutRowDef.optString("name", null);
869
870        boolean alwaysSelected = layoutRowDef.optBoolean("alwaysSelected", false);
871        boolean selectedByDefault = layoutRowDef.optBoolean("selectedByDefault", true);
872
873        Map<String, Map<String, Serializable>> properties = importPropsByMode(layoutRowDef.optJSONObject("properties"));
874
875        List<WidgetReference> widgets = new ArrayList<WidgetReference>();
876        JSONArray jwidgets = layoutRowDef.optJSONArray("widgets");
877        if (jwidgets != null) {
878            for (Object item : jwidgets) {
879                if (item instanceof String) {
880                    // BBB
881                    widgets.add(new WidgetReferenceImpl((String) item));
882                } else {
883                    widgets.add(importWidgetReference((JSONObject) item));
884                }
885            }
886        }
887        return new LayoutRowDefinitionImpl(name, properties, widgets, alwaysSelected, selectedByDefault);
888    }
889
890    /**
891     * @since 5.5
892     * @param widgetDef
893     * @param ctx
894     * @param widgetConverters
895     * @return
896     */
897    @SuppressWarnings("deprecation")
898    public static JSONObject exportToJson(WidgetDefinition widgetDef, LayoutConversionContext ctx,
899            List<WidgetDefinitionConverter> widgetConverters) {
900        JSONObject json = new JSONObject();
901        json.element("name", widgetDef.getName());
902        json.element("type", widgetDef.getType());
903        json.element("typeCategory", widgetDef.getTypeCategory());
904        JSONObject labels = exportStringPropsToJson(widgetDef.getLabels());
905        if (!labels.isEmpty()) {
906            json.element("labels", labels);
907        }
908        JSONObject helpLabels = exportStringPropsToJson(widgetDef.getHelpLabels());
909        if (!helpLabels.isEmpty()) {
910            json.element("helpLabels", helpLabels);
911        }
912        json.element("translated", widgetDef.isTranslated());
913        json.element("handlingLabels", widgetDef.isHandlingLabels());
914        JSONObject widgetModes = exportStringPropsToJson(widgetDef.getModes());
915        if (!widgetModes.isEmpty()) {
916            json.element("widgetModes", widgetModes);
917        }
918
919        JSONArray fields = new JSONArray();
920        FieldDefinition[] fieldDefs = widgetDef.getFieldDefinitions();
921        if (fieldDefs != null) {
922            for (FieldDefinition fieldDef : fieldDefs) {
923                fields.add(exportToJson(fieldDef));
924            }
925        }
926        if (!fields.isEmpty()) {
927            json.element("fields", fields);
928        }
929
930        JSONArray subWidgets = new JSONArray();
931        WidgetDefinition[] subWidgetDefs = widgetDef.getSubWidgetDefinitions();
932        if (subWidgetDefs != null) {
933            for (WidgetDefinition wDef : subWidgetDefs) {
934                subWidgets.add(exportToJson(wDef, ctx, widgetConverters));
935            }
936        }
937        if (!subWidgets.isEmpty()) {
938            json.element("subWidgets", subWidgets);
939        }
940
941        JSONArray subWidgetRefs = new JSONArray();
942        WidgetReference[] subWidgetRefDefs = widgetDef.getSubWidgetReferences();
943        if (subWidgetRefDefs != null) {
944            for (WidgetReference ref : subWidgetRefDefs) {
945                subWidgetRefs.add(exportToJson(ref));
946            }
947        }
948        if (!subWidgetRefs.isEmpty()) {
949            json.element("subWidgetRefs", subWidgetRefs);
950        }
951
952        JSONObject props = exportPropsByModeToJson(widgetDef.getProperties());
953        if (!props.isEmpty()) {
954            json.element("properties", props);
955        }
956        JSONObject widgetModeProps = exportPropsByModeToJson(widgetDef.getWidgetModeProperties());
957        if (!widgetModeProps.isEmpty()) {
958            json.element("propertiesByWidgetMode", widgetModeProps);
959        }
960
961        JSONObject controls = exportPropsByModeToJson(widgetDef.getControls());
962        if (!controls.isEmpty()) {
963            json.element("controls", controls);
964        }
965
966        JSONArray selectOptions = new JSONArray();
967        WidgetSelectOption[] selectOptionDefs = widgetDef.getSelectOptions();
968        if (selectOptionDefs != null) {
969            for (WidgetSelectOption selectOptionDef : selectOptionDefs) {
970                selectOptions.add(exportToJson(selectOptionDef));
971            }
972        }
973        if (!selectOptions.isEmpty()) {
974            json.element("selectOptions", selectOptions);
975        }
976
977        JSONObject renderingInfos = exportRenderingInfosByModeToJson(widgetDef.getRenderingInfos());
978        if (!renderingInfos.isEmpty()) {
979            json.element("renderingInfos", renderingInfos);
980        }
981
982        List<String> caliases = widgetDef.getAliases();
983        if (caliases != null && !caliases.isEmpty()) {
984            JSONArray aliases = new JSONArray();
985            for (String alias : caliases) {
986                aliases.add(alias);
987            }
988            json.element("aliases", aliases);
989        }
990
991        return json;
992    }
993
994    @SuppressWarnings({ "unchecked" })
995    public static WidgetDefinition importWidgetDefinition(JSONObject widgetDef) {
996        String name = widgetDef.getString("name");
997        String type = widgetDef.getString("type");
998        String typeCategory = widgetDef.optString("typeCategory");
999        Map<String, String> labels = importStringProps(widgetDef.optJSONObject("labels"));
1000        Map<String, String> helpLabels = importStringProps(widgetDef.optJSONObject("helpLabels"));
1001        boolean translated = widgetDef.optBoolean("translated", false);
1002        boolean handlingLabels = widgetDef.optBoolean("handlingLabels", false);
1003        Map<String, String> modes = importStringProps(widgetDef.optJSONObject("widgetModes"));
1004
1005        List<FieldDefinition> fieldDefinitions = new ArrayList<FieldDefinition>();
1006        JSONArray jfields = widgetDef.optJSONArray("fields");
1007        if (jfields != null) {
1008            for (Object item : jfields) {
1009                fieldDefinitions.add(importFieldDefinition((JSONObject) item));
1010            }
1011        }
1012
1013        List<WidgetDefinition> subWidgets = new ArrayList<WidgetDefinition>();
1014        JSONArray jsubwidgets = widgetDef.optJSONArray("subWidgets");
1015        if (jsubwidgets != null) {
1016            for (Object item : jsubwidgets) {
1017                subWidgets.add(importWidgetDefinition((JSONObject) item));
1018            }
1019        }
1020
1021        List<WidgetReference> subWidgetRefs = new ArrayList<WidgetReference>();
1022        JSONArray jsubwidgetRefs = widgetDef.optJSONArray("subWidgetRefs");
1023        if (jsubwidgetRefs != null) {
1024            for (Object item : jsubwidgetRefs) {
1025                subWidgetRefs.add(importWidgetReference((JSONObject) item));
1026            }
1027        }
1028
1029        Map<String, Map<String, Serializable>> properties = importPropsByMode(widgetDef.optJSONObject("properties"));
1030        Map<String, Map<String, Serializable>> widgetModeProperties = importPropsByMode(widgetDef.optJSONObject("propertiesByWidgetMode"));
1031        Map<String, Map<String, Serializable>> controls = importPropsByMode(widgetDef.optJSONObject("controls"));
1032
1033        // select options
1034        List<WidgetSelectOption> selectOptions = new ArrayList<WidgetSelectOption>();
1035        JSONArray jselectOptions = widgetDef.optJSONArray("selectOptions");
1036        if (jselectOptions != null) {
1037            for (Object item : jselectOptions) {
1038                selectOptions.add(importWidgetSelectionOption((JSONObject) item));
1039            }
1040        }
1041
1042        Map<String, List<RenderingInfo>> renderingInfos = importRenderingInfosByMode(widgetDef.optJSONObject("renderingInfos"));
1043
1044        List<String> aliases = new ArrayList<String>();
1045        JSONArray jaliases = widgetDef.optJSONArray("aliases");
1046        if (jaliases != null) {
1047            aliases.addAll(jaliases);
1048        }
1049
1050        WidgetDefinitionImpl res = new WidgetDefinitionImpl(name, type, labels, helpLabels, translated, modes,
1051                fieldDefinitions.toArray(new FieldDefinition[] {}), properties, widgetModeProperties,
1052                subWidgets.toArray(new WidgetDefinition[] {}), selectOptions.toArray(new WidgetSelectOption[] {}));
1053        res.setRenderingInfos(renderingInfos);
1054        res.setSubWidgetReferences(subWidgetRefs.toArray(new WidgetReference[] {}));
1055        res.setHandlingLabels(handlingLabels);
1056        res.setControls(controls);
1057        res.setTypeCategory(typeCategory);
1058        res.setAliases(aliases);
1059        return res;
1060    }
1061
1062    public static JSONObject exportToJson(FieldDefinition fieldDef) {
1063        JSONObject json = new JSONObject();
1064        json.element("fieldName", fieldDef.getFieldName());
1065        json.element("schemaName", fieldDef.getSchemaName());
1066        json.element("propertyName", fieldDef.getPropertyName());
1067        return json;
1068    }
1069
1070    public static FieldDefinition importFieldDefinition(JSONObject fieldDef) {
1071        // ignore property name: it can be deduced from schema and field name
1072        FieldDefinition res = new FieldDefinitionImpl(fieldDef.optString("schemaName", null),
1073                fieldDef.getString("fieldName"));
1074        return res;
1075    }
1076
1077    public static JSONObject exportToJson(WidgetReference widgetRef) {
1078        JSONObject json = new JSONObject();
1079        json.element("name", widgetRef.getName());
1080        json.element("category", widgetRef.getCategory());
1081        return json;
1082    }
1083
1084    public static WidgetReference importWidgetReference(JSONObject widgetRef) {
1085        WidgetReference res = new WidgetReferenceImpl(widgetRef.optString("category"),
1086                widgetRef.optString("name", null));
1087        return res;
1088    }
1089
1090    /**
1091     * @since 7.3
1092     */
1093    public static JSONObject exportToJson(Layout layout) {
1094        JSONObject json = new JSONObject();
1095        json.element("name", layout.getName());
1096
1097        String type = layout.getType();
1098        if (type != null) {
1099            json.element("type", type);
1100        }
1101
1102        String typeCat = layout.getTypeCategory();
1103        if (typeCat != null) {
1104            json.element("typeCategory", typeCat);
1105        }
1106
1107        json.element("mode", layout.getMode());
1108
1109        String template = layout.getTemplate();
1110        if (template != null) {
1111            json.element("template", template);
1112        }
1113
1114        JSONObject props = exportPropsToJson(layout.getProperties());
1115        if (!props.isEmpty()) {
1116            json.element("properties", props);
1117        }
1118
1119        JSONArray rows = new JSONArray();
1120        LayoutRow[] lRows = layout.getRows();
1121        if (lRows != null) {
1122            for (LayoutRow lRow : lRows) {
1123                rows.add(exportToJson(lRow));
1124            }
1125        }
1126
1127        if (!rows.isEmpty()) {
1128            json.element("rows", rows);
1129        }
1130
1131        return json;
1132    }
1133
1134    /**
1135     * @since 7.3
1136     */
1137    public static JSONObject exportToJson(LayoutRow layoutRow) {
1138        JSONObject json = new JSONObject();
1139        String name = layoutRow.getName();
1140        if (name != null) {
1141            json.element("name", name);
1142        }
1143        // fill selection info only if that's not the default value from the
1144        // definition
1145        if (layoutRow.isAlwaysSelected()) {
1146            json.element("alwaysSelected", true);
1147        }
1148        if (!layoutRow.isSelectedByDefault()) {
1149            json.element("selectedByDefault", false);
1150        }
1151        layoutRow.isSelectedByDefault();
1152        JSONObject props = exportPropsToJson(layoutRow.getProperties());
1153        if (!props.isEmpty()) {
1154            json.element("properties", props);
1155        }
1156        JSONArray widgets = new JSONArray();
1157        Widget[] rowWidgets = layoutRow.getWidgets();
1158        if (rowWidgets != null) {
1159            for (Widget widget : rowWidgets) {
1160                widgets.add(exportToJson(widget));
1161            }
1162        }
1163        if (!widgets.isEmpty()) {
1164            json.element("widgets", widgets);
1165        }
1166        return json;
1167    }
1168
1169    /**
1170     * @since 7.3
1171     */
1172    public static JSONObject exportToJson(Widget widget) {
1173        JSONObject json = new JSONObject();
1174        json.element("name", widget.getName());
1175        json.element("type", widget.getType());
1176        json.element("typeCategory", widget.getTypeCategory());
1177        json.element("mode", widget.getMode());
1178        json.element("label", widget.getLabel());
1179        json.element("helpLabel", widget.getHelpLabel());
1180        json.element("translated", widget.isTranslated());
1181        json.element("handlingLabels", widget.isHandlingLabels());
1182        JSONArray fields = new JSONArray();
1183        FieldDefinition[] fieldDefs = widget.getFieldDefinitions();
1184        if (fieldDefs != null) {
1185            for (FieldDefinition fieldDef : fieldDefs) {
1186                fields.add(exportToJson(fieldDef));
1187            }
1188        }
1189        if (!fields.isEmpty()) {
1190            json.element("fields", fields);
1191        }
1192
1193        JSONArray subWidgets = new JSONArray();
1194        Widget[] wSubWidgets = widget.getSubWidgets();
1195        if (wSubWidgets != null) {
1196            for (Widget wDef : wSubWidgets) {
1197                subWidgets.add(exportToJson(wDef));
1198            }
1199        }
1200        if (!subWidgets.isEmpty()) {
1201            json.element("subWidgets", subWidgets);
1202        }
1203
1204        JSONObject props = exportPropsToJson(widget.getProperties());
1205        if (!props.isEmpty()) {
1206            json.element("properties", props);
1207        }
1208
1209        JSONObject controls = exportPropsToJson(widget.getControls());
1210        if (!controls.isEmpty()) {
1211            json.element("controls", controls);
1212        }
1213
1214        JSONArray selectOptions = new JSONArray();
1215        WidgetSelectOption[] selectOptionDefs = widget.getSelectOptions();
1216        if (selectOptionDefs != null) {
1217            for (WidgetSelectOption selectOptionDef : selectOptionDefs) {
1218                selectOptions.add(exportToJson(selectOptionDef));
1219            }
1220        }
1221        if (!selectOptions.isEmpty()) {
1222            json.element("selectOptions", selectOptions);
1223        }
1224
1225        return json;
1226    }
1227
1228    public static JSONObject exportToJson(WidgetSelectOption selectOption) {
1229        JSONObject json = new JSONObject();
1230        Serializable value = selectOption.getValue();
1231        boolean isMulti = selectOption instanceof WidgetSelectOptions;
1232        if (isMulti) {
1233            json.element("multiple", true);
1234        } else {
1235            json.element("multiple", false);
1236        }
1237        if (value != null) {
1238            json.element("value", value);
1239        }
1240        String var = selectOption.getVar();
1241        if (var != null) {
1242            json.element("var", var);
1243        }
1244        String itemLabel = selectOption.getItemLabel();
1245        if (itemLabel != null) {
1246            json.element("itemLabel", itemLabel);
1247        }
1248        Map<String, String> labels = selectOption.getItemLabels();
1249        if (labels != null && !labels.isEmpty()) {
1250            JSONObject jsonLabels = new JSONObject();
1251            for (Map.Entry<String, String> entry : labels.entrySet()) {
1252                jsonLabels.put(entry.getKey(), entry.getValue());
1253            }
1254            json.element("itemLabels", jsonLabels);
1255        }
1256        String itemValue = selectOption.getItemValue();
1257        if (itemValue != null) {
1258            json.element("itemValue", itemValue);
1259        }
1260        Serializable itemDisabled = selectOption.getItemDisabled();
1261        if (itemDisabled != null) {
1262            json.element("itemDisabled", itemDisabled);
1263        }
1264        Serializable itemRendered = selectOption.getItemRendered();
1265        if (itemRendered != null) {
1266            json.element("itemRendered", itemRendered);
1267        }
1268        if (isMulti) {
1269            WidgetSelectOptions selectOptions = (WidgetSelectOptions) selectOption;
1270            String ordering = selectOptions.getOrdering();
1271            if (ordering != null) {
1272                json.element("ordering", ordering);
1273            }
1274            Boolean caseSensitive = selectOptions.getCaseSensitive();
1275            if (caseSensitive != null) {
1276                json.element("caseSensitive", caseSensitive);
1277            }
1278        }
1279        return json;
1280    }
1281
1282    @SuppressWarnings("unchecked")
1283    public static WidgetSelectOption importWidgetSelectionOption(JSONObject selectOption) {
1284        boolean isMulti = selectOption.getBoolean("multiple");
1285        Serializable value = selectOption.optString("value", null);
1286        String var = selectOption.optString("var", null);
1287        String itemLabel = selectOption.optString("itemLabel", null);
1288
1289        Map<String, String> labels = new HashMap<String, String>();
1290        JSONObject jsonLabels = selectOption.optJSONObject("itemLabels");
1291        if (jsonLabels != null && !jsonLabels.isNullObject()) {
1292            labels.putAll(jsonLabels);
1293        }
1294
1295        String itemValue = selectOption.optString("itemValue", null);
1296        Serializable itemDisabled = selectOption.optString("itemDisabled", null);
1297        Serializable itemRendered = selectOption.optString("itemRendered", null);
1298        if (isMulti) {
1299            String ordering = selectOption.optString("ordering", null);
1300            Boolean caseSensitive = null;
1301            if (selectOption.has("caseSensitive")) {
1302                caseSensitive = new Boolean(selectOption.getBoolean("caseSensitive"));
1303            }
1304            WidgetSelectOptionsImpl res = new WidgetSelectOptionsImpl(value, var, itemLabel, itemValue, itemDisabled,
1305                    itemRendered, ordering, caseSensitive);
1306            res.setItemLabels(labels);
1307            return res;
1308        } else {
1309            WidgetSelectOptionImpl res = new WidgetSelectOptionImpl(value, var, itemLabel, itemValue, itemDisabled,
1310                    itemRendered);
1311            res.setItemLabels(labels);
1312            return res;
1313        }
1314    }
1315
1316    public static JSONObject exportPropsByModeToJson(Map<String, Map<String, Serializable>> propsByMode) {
1317        JSONObject props = new JSONObject();
1318        if (propsByMode != null) {
1319            List<String> defModes = new ArrayList<String>(propsByMode.keySet());
1320            // sort so that order is deterministic
1321            Collections.sort(defModes);
1322            for (String defMode : defModes) {
1323                props.element(defMode, exportPropsToJson(propsByMode.get(defMode)));
1324            }
1325        }
1326        return props;
1327    }
1328
1329    @SuppressWarnings("unchecked")
1330    public static Map<String, Map<String, Serializable>> importPropsByMode(JSONObject propsByMode) {
1331        Map<String, Map<String, Serializable>> props = new HashMap<String, Map<String, Serializable>>();
1332        if (propsByMode != null && !propsByMode.isNullObject()) {
1333            List<String> defModes = new ArrayList<String>(propsByMode.keySet());
1334            // sort so that order is deterministic
1335            Collections.sort(defModes);
1336            for (String defMode : defModes) {
1337                props.put(defMode, importProps(propsByMode.getJSONObject(defMode)));
1338            }
1339        }
1340        return props;
1341    }
1342
1343    @SuppressWarnings({ "rawtypes" })
1344    public static JSONObject exportPropsToJson(Map<String, Serializable> defProps) {
1345        JSONObject props = new JSONObject();
1346        if (defProps != null) {
1347            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1348            // sort so that order is deterministic
1349            Collections.sort(defPropNames);
1350            for (String defPropName : defPropNames) {
1351                Serializable value = defProps.get(defPropName);
1352                if (value instanceof Collection) {
1353                    JSONArray array = new JSONArray();
1354                    array.addAll((Collection) value);
1355                    props.element(defPropName, array);
1356                } else if (value instanceof Object[]) {
1357                    JSONArray array = new JSONArray();
1358                    for (Object item : (Object[]) value) {
1359                        array.add(item);
1360                    }
1361                    props.element(defPropName, array);
1362                } else {
1363                    props.element(defPropName, value);
1364                }
1365            }
1366        }
1367        return props;
1368    }
1369
1370    @SuppressWarnings("unchecked")
1371    public static Map<String, Serializable> importProps(JSONObject defProps) {
1372        Map<String, Serializable> props = new HashMap<String, Serializable>();
1373        if (defProps != null && !defProps.isNullObject()) {
1374            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1375            // sort so that order is deterministic
1376            Collections.sort(defPropNames);
1377            for (String defPropName : defPropNames) {
1378                Object value = defProps.opt(defPropName);
1379                if (value instanceof JSONArray) {
1380                    ArrayList<Object> listValue = new ArrayList<Object>();
1381                    listValue.addAll(((JSONArray) value));
1382                } else {
1383                    props.put(defPropName, value.toString());
1384                }
1385            }
1386        }
1387        return props;
1388    }
1389
1390    public static JSONObject exportStringPropsToJson(Map<String, String> defProps) {
1391        JSONObject props = new JSONObject();
1392        if (defProps != null) {
1393            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1394            // sort so that order is deterministic
1395            Collections.sort(defPropNames);
1396            for (String defPropName : defPropNames) {
1397                props.element(defPropName, defProps.get(defPropName));
1398            }
1399        }
1400        return props;
1401    }
1402
1403    public static Map<String, String> importStringProps(JSONObject defProps) {
1404        Map<String, String> props = new HashMap<String, String>();
1405        if (defProps != null && !defProps.isNullObject()) {
1406            for (Object item : defProps.keySet()) {
1407                String key = (String) item;
1408                props.put(key, defProps.getString(key));
1409            }
1410        }
1411        return props;
1412    }
1413
1414    public static JSONObject exportRenderingInfosByModeToJson(Map<String, List<RenderingInfo>> infosByMode) {
1415        JSONObject props = new JSONObject();
1416        if (infosByMode != null) {
1417            List<String> defModes = new ArrayList<String>(infosByMode.keySet());
1418            // sort so that order is deterministic
1419            Collections.sort(defModes);
1420            for (String defMode : defModes) {
1421                props.element(defMode, exportRenderingInfosToJson(infosByMode.get(defMode)));
1422            }
1423        }
1424        return props;
1425    }
1426
1427    @SuppressWarnings("unchecked")
1428    public static Map<String, List<RenderingInfo>> importRenderingInfosByMode(JSONObject infosByMode) {
1429        Map<String, List<RenderingInfo>> props = new HashMap<String, List<RenderingInfo>>();
1430        if (infosByMode != null && !infosByMode.isNullObject()) {
1431            List<String> defModes = new ArrayList<String>(infosByMode.keySet());
1432            // sort so that order is deterministic
1433            Collections.sort(defModes);
1434            for (String defMode : defModes) {
1435                props.put(defMode, importRenderingInfos(infosByMode.getJSONArray(defMode)));
1436            }
1437        }
1438        return props;
1439    }
1440
1441    public static JSONArray exportRenderingInfosToJson(List<RenderingInfo> infos) {
1442        JSONArray jinfos = new JSONArray();
1443        if (infos != null) {
1444            for (RenderingInfo info : infos) {
1445                jinfos.add(exportToJson(info));
1446            }
1447        }
1448        return jinfos;
1449    }
1450
1451    public static List<RenderingInfo> importRenderingInfos(JSONArray jinfos) {
1452        List<RenderingInfo> infos = new ArrayList<RenderingInfo>();
1453        if (jinfos != null) {
1454            for (Object item : jinfos) {
1455                infos.add(importRenderingInfo((JSONObject) item));
1456            }
1457        }
1458        return infos;
1459    }
1460
1461    public static JSONObject exportToJson(RenderingInfo info) {
1462        JSONObject json = new JSONObject();
1463        json.element("level", info.getLevel());
1464        json.element("message", info.getMessage());
1465        json.element("translated", info.isTranslated());
1466        return json;
1467    }
1468
1469    public static RenderingInfo importRenderingInfo(JSONObject fieldDef) {
1470        RenderingInfo res = new RenderingInfoImpl(fieldDef.optString("level", ""), fieldDef.optString("message"),
1471                fieldDef.optBoolean("translated", false));
1472        return res;
1473    }
1474
1475}