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