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